Compare commits

...

22 Commits

Author SHA1 Message Date
c4615591c9 fixing label names, fixes #11 2019-03-30 00:31:40 +01:00
25e3b599e7 fixing label names 2019-03-30 00:15:50 +01:00
5502a3e3ee optimized name checking, no longer depends on scopedname 2019-03-28 21:30:30 +01:00
62ceace941 block names are global (unscoped) 2019-03-25 23:46:58 +01:00
7114d3193c some cleanups in library asm code 2019-03-21 22:36:46 +01:00
f6bc69139d added some example images to the index page of the docs 2019-03-19 21:39:01 +01:00
f3fc2fe523 irq handler saves zeropage scratch registers, fixes #8 2019-03-19 01:22:26 +01:00
1e045b6a62 fixed multi-return value assignment 2019-03-18 04:44:20 +01:00
747c9604dd improve ast check for multiple returnvalues assignment 2019-03-18 04:01:25 +01:00
1e5b2e0be3 for loops can now be over an iterable literal value directly (don't require a variable to hold the iterable) 2019-03-17 23:58:07 +01:00
0820716e7b added sqrt16() integer square root 2019-03-16 19:25:47 +01:00
191707cd37 added new c64utils.str2(u)word that doesn't use kernel float routines
fixed processing of register pair return value of asmsub
2019-03-16 17:50:59 +01:00
223bab21aa less verbose anon label names 2019-03-16 00:11:04 +01:00
563122ac92 stricter argument check for boolean operator 2019-03-15 23:34:15 +01:00
bc9d00922e implemented difference between printing and writing text in vm screen 2019-03-15 23:27:54 +01:00
d9d83248fe implemented strlen() function 2019-03-15 23:10:26 +01:00
f2397527f1 improved text output in stackvm 2019-03-13 22:45:12 +01:00
bf3caaefe1 stackvm now uses a proper instruction pointer call stack instead of instruction linking 2019-03-13 22:00:41 +01:00
1aaf854ef7 identified issue with single instruction linking in vm 2019-03-12 21:59:40 +01:00
ce40f6f862 defined a few more sysasm routines 2019-03-11 22:30:32 +01:00
a349599943 serious endless for loop bug in stackvm because Z and N flags weren't set properly, now fixed 2019-03-11 22:02:00 +01:00
ac7faa8d25 stackvm can now intercept system asm calls (to a rom address) 2019-03-11 02:05:30 +01:00
35 changed files with 1296 additions and 827 deletions

View File

@ -235,7 +235,7 @@ asmsub GETIN () -> clobbers(X,Y) -> (ubyte @ A) = $FFE4 ; (via 810 ($32A))
asmsub CLALL () -> clobbers(A,X) -> () = $FFE7 ; (via 812 ($32C)) close all files asmsub CLALL () -> clobbers(A,X) -> () = $FFE7 ; (via 812 ($32C)) close all files
asmsub UDTIM () -> clobbers(A,X) -> () = $FFEA ; update the software clock asmsub UDTIM () -> clobbers(A,X) -> () = $FFEA ; update the software clock
asmsub SCREEN () -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFED ; read number of screen rows and columns asmsub SCREEN () -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFED ; read number of screen rows and columns
asmsub PLOT (ubyte dir @ Pc, ubyte col @ Y, ubyte row @ X) -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF0 ; read/set position of cursor on screen. See c64scr.PLOT for a 'safe' wrapper that preserves X. asmsub PLOT (ubyte dir @ Pc, ubyte col @ Y, ubyte row @ X) -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
asmsub IOBASE () -> clobbers() -> (uword @ XY) = $FFF3 ; read base address of I/O devices asmsub IOBASE () -> clobbers() -> (uword @ XY) = $FFF3 ; read base address of I/O devices
; ---- end of C64 kernal routines ---- ; ---- end of C64 kernal routines ----

View File

@ -54,40 +54,39 @@ asmsub ubyte2hex (ubyte value @ A) -> clobbers() -> (ubyte @ A, ubyte @ Y) {
pha pha
and #$0f and #$0f
tax tax
ldy hex_digits,x ldy _hex_digits,x
pla pla
lsr a lsr a
lsr a lsr a
lsr a lsr a
lsr a lsr a
tax tax
lda hex_digits,x lda _hex_digits,x
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
rts rts
hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
}} }}
} }
str word2hex_output = "1234" ; 0-terminated, to make printing easier
asmsub uword2hex (uword value @ AY) -> clobbers(A,Y) -> () { asmsub uword2hex (uword value @ AY) -> clobbers(A,Y) -> () {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string into memory 'word2hex_output' ; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{ %asm {{
sta c64.SCRATCH_ZPREG sta c64.SCRATCH_ZPREG
tya tya
jsr ubyte2hex jsr ubyte2hex
sta word2hex_output sta output
sty word2hex_output+1 sty output+1
lda c64.SCRATCH_ZPREG lda c64.SCRATCH_ZPREG
jsr ubyte2hex jsr ubyte2hex
sta word2hex_output+2 sta output+2
sty word2hex_output+3 sty output+3
rts rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
}} }}
} }
ubyte[3] word2bcd_bcdbuff = [0, 0, 0]
asmsub uword2bcd (uword value @ AY) -> clobbers(A,Y) -> () { asmsub uword2bcd (uword value @ AY) -> clobbers(A,Y) -> () {
; Convert an 16 bit binary value to BCD ; Convert an 16 bit binary value to BCD
; ;
@ -106,22 +105,22 @@ asmsub uword2bcd (uword value @ AY) -> clobbers(A,Y) -> () {
sei ; disable interrupts because of bcd math sei ; disable interrupts because of bcd math
sed ; switch to decimal mode sed ; switch to decimal mode
lda #0 ; ensure the result is clear lda #0 ; ensure the result is clear
sta word2bcd_bcdbuff+0 sta bcdbuff+0
sta word2bcd_bcdbuff+1 sta bcdbuff+1
sta word2bcd_bcdbuff+2 sta bcdbuff+2
ldy #16 ; the number of source bits ldy #16 ; the number of source bits
- asl c64.SCRATCH_ZPB1 ; shift out one bit - asl c64.SCRATCH_ZPB1 ; shift out one bit
rol c64.SCRATCH_ZPREG rol c64.SCRATCH_ZPREG
lda word2bcd_bcdbuff+0 ; and add into result lda bcdbuff+0 ; and add into result
adc word2bcd_bcdbuff+0 adc bcdbuff+0
sta word2bcd_bcdbuff+0 sta bcdbuff+0
lda word2bcd_bcdbuff+1 ; propagating any carry lda bcdbuff+1 ; propagating any carry
adc word2bcd_bcdbuff+1 adc bcdbuff+1
sta word2bcd_bcdbuff+1 sta bcdbuff+1
lda word2bcd_bcdbuff+2 ; ... thru whole result lda bcdbuff+2 ; ... thru whole result
adc word2bcd_bcdbuff+2 adc bcdbuff+2
sta word2bcd_bcdbuff+2 sta bcdbuff+2
dey ; and repeat for next bit dey ; and repeat for next bit
bne - bne -
cld ; back to binary cld ; back to binary
@ -130,23 +129,24 @@ asmsub uword2bcd (uword value @ AY) -> clobbers(A,Y) -> () {
cli ; enable interrupts again (only if they were enabled before) cli ; enable interrupts again (only if they were enabled before)
+ rts + rts
_had_irqd .byte 0 _had_irqd .byte 0
bcdbuff .byte 0,0,0
}} }}
} }
ubyte[5] word2decimal_output = 0 asmsub uword2decimal (uword value @ AY) -> clobbers(A) -> (ubyte @ Y) {
asmsub uword2decimal (uword value @ AY) -> clobbers(A,Y) -> () { ; ---- convert 16 bit uword in A/Y into 0-terminated decimal string into memory 'uword2decimal.output'
; ---- convert 16 bit uword in A/Y into decimal string into memory 'word2decimal_output' ; returns length of resulting string in Y
%asm {{ %asm {{
jsr uword2bcd jsr uword2bcd
lda word2bcd_bcdbuff+2 lda uword2bcd.bcdbuff+2
clc clc
adc #'0' adc #'0'
sta word2decimal_output sta output
ldy #1 ldy #1
lda word2bcd_bcdbuff+1 lda uword2bcd.bcdbuff+1
jsr + jsr +
lda word2bcd_bcdbuff+0 lda uword2bcd.bcdbuff+0
+ pha + pha
lsr a lsr a
@ -155,153 +155,135 @@ asmsub uword2decimal (uword value @ AY) -> clobbers(A,Y) -> () {
lsr a lsr a
clc clc
adc #'0' adc #'0'
sta word2decimal_output,y sta output,y
iny iny
pla pla
and #$0f and #$0f
adc #'0' adc #'0'
sta word2decimal_output,y sta output,y
iny iny
rts
}}
}
asmsub str2byte (str string @ AY) -> clobbers(Y) -> (byte @ A) {
%asm {{
; -- convert string (address in A/Y) to byte in A
; doesn't use any kernal routines
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
cmp #'-'
beq +
jmp str2ubyte._enter
+ inc c64.SCRATCH_ZPWORD1
bne +
inc c64.SCRATCH_ZPWORD1+1
+ jsr str2ubyte._enter
eor #$ff
sec
adc #0
rts
}}
}
asmsub str2ubyte (str string @ AY) -> clobbers(Y) -> (ubyte @ A) {
%asm {{
; -- convert string (address in A/Y) to ubyte in A
; doesn't use any kernal routines
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
_enter jsr _numlen ; Y= slen
lda #0 lda #0
dey sta output,y
bpl +
rts rts
+ lda (c64.SCRATCH_ZPWORD1),y
sec
sbc #'0'
dey
bpl +
rts
+ sta c64.SCRATCH_ZPREG ;result
lda (c64.SCRATCH_ZPWORD1),y
sec
sbc #'0'
asl a
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
clc
adc c64.SCRATCH_ZPREG
dey
bpl +
rts
+ sta c64.SCRATCH_ZPREG
lda (c64.SCRATCH_ZPWORD1),y
tay
lda _hundreds-'0',y
clc
adc c64.SCRATCH_ZPREG
rts
_hundreds .byte 0, 100, 200
_numlen output .text "00000", $00 ; 0 terminated
;-- return the length of the numeric string at ZPWORD1, in Y
}}
}
asmsub str2uword(str string @ AY) -> clobbers() -> (uword @ AY) {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
_result = c64.SCRATCH_ZPWORD2
sta _mod+1
sty _mod+2
ldy #0 ldy #0
- lda (c64.SCRATCH_ZPWORD1),y sty _result
cmp #'0' sty _result+1
bmi + _mod lda $ffff,y ; modified
cmp #':' ; one after '9' sec
sbc #48
bpl + bpl +
iny _done ; return result
bne - lda _result
+ rts ldy _result+1
rts
+ cmp #10
bcs _done
; add digit to result
pha
jsr _result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _mod
; never reached
_result_times_10 ; (W*4 + W)*2
lda _result+1
sta c64.SCRATCH_ZPREG
lda _result
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
clc
adc _result
sta _result
lda c64.SCRATCH_ZPREG
adc _result+1
asl _result
rol a
sta _result+1
rts
}} }}
} }
asmsub c64flt_FREADSTR (ubyte length @ A) -> clobbers(A,X,Y) -> () = $b7b5 ; @todo needed for (slow) str conversion below asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) {
asmsub c64flt_GETADR () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b7f7 ; @todo needed for (slow) str conversion below ; -- returns the signed word value of the string number argument in AY
asmsub c64flt_FTOSWORDYA () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b1aa ; @todo needed for (slow) str conversion below ; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
asmsub str2uword(str string @ AY) -> clobbers() -> (uword @ AY) {
%asm {{ %asm {{
;-- convert string (address in A/Y) to uword number in A/Y _result = c64.SCRATCH_ZPWORD2
; @todo don't use the (slow) kernel floating point conversion sta c64.SCRATCH_ZPWORD1
sta $22 sty c64.SCRATCH_ZPWORD1+1
sty $23
jsr _strlen2233
tya
stx c64.SCRATCH_ZPREGX
jsr c64flt_FREADSTR ; string to fac1
jsr c64flt_GETADR ; fac1 to unsigned word in Y/A
ldx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPREG
tya
ldy c64.SCRATCH_ZPREG
rts
_strlen2233
;-- return the length of the (zero-terminated) string at $22/$23, in Y
ldy #0 ldy #0
- lda ($22),y sty _result
sty _result+1
sty _negative
lda (c64.SCRATCH_ZPWORD1),y
cmp #'+'
bne +
iny
+ cmp #'-'
bne _parse
inc _negative
iny
_parse lda (c64.SCRATCH_ZPWORD1),y
sec
sbc #48
bpl _digit
_done ; return result
lda _negative
beq + beq +
iny sec
bne - lda #0
+ rts sbc _result
}} sta _result
} lda #0
sbc _result+1
asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) { sta _result+1
%asm {{ + lda _result
;-- convert string (address in A/Y) to signed word number in A/Y ldy _result+1
; @todo don't use the (slow) kernel floating point conversion
sta $22
sty $23
jsr str2uword._strlen2233
tya
stx c64.SCRATCH_ZPREGX
jsr c64flt_FREADSTR ; string to fac1
jsr c64flt_FTOSWORDYA ; fac1 to unsigned word in Y/A
ldx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPREG
tya
ldy c64.SCRATCH_ZPREG
rts rts
_digit cmp #10
bcs _done
; add digit to result
pha
jsr str2uword._result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _parse
; never reached
_negative .byte 0
}} }}
} }
; @todo string to 32 bit unsigned integer http://www.6502.org/source/strings/ascii-to-32bit.html
asmsub set_irqvec_excl() -> clobbers(A) -> () { asmsub set_irqvec_excl() -> clobbers(A) -> () {
%asm {{ %asm {{
sei sei
@ -311,7 +293,9 @@ asmsub set_irqvec_excl() -> clobbers(A) -> () {
sta c64.CINV+1 sta c64.CINV+1
cli cli
rts rts
_irq_handler jsr irq.irq _irq_handler jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff lda #$ff
sta c64.VICIRQ ; acknowledge raster irq sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt lda c64.CIA1ICR ; acknowledge CIA1 interrupt
@ -328,10 +312,64 @@ asmsub set_irqvec() -> clobbers(A) -> () {
sta c64.CINV+1 sta c64.CINV+1
cli cli
rts rts
_irq_handler jsr irq.irq _irq_handler jsr _irq_handler_init
jsr irq.irq
jsr _irq_handler_end
jmp c64.IRQDFRT ; continue with normal kernel irq routine jmp c64.IRQDFRT ; continue with normal kernel irq routine
}} _irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
stx IRQ_X_REG
lda c64.SCRATCH_ZPB1
sta IRQ_SCRATCH_ZPB1
lda c64.SCRATCH_ZPREG
sta IRQ_SCRATCH_ZPREG
lda c64.SCRATCH_ZPREGX
sta IRQ_SCRATCH_ZPREGX
lda c64.SCRATCH_ZPWORD1
sta IRQ_SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD2
sta IRQ_SCRATCH_ZPWORD2
lda c64.SCRATCH_ZPWORD2+1
sta IRQ_SCRATCH_ZPWORD2+1
; stack protector; make sure we don't clobber the top of the evaluation stack
dex
dex
dex
dex
dex
dex
rts
_irq_handler_end
; restore all zp scratch registers and the X register
lda IRQ_SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
lda IRQ_SCRATCH_ZPREG
sta c64.SCRATCH_ZPREG
lda IRQ_SCRATCH_ZPREGX
sta c64.SCRATCH_ZPREGX
lda IRQ_SCRATCH_ZPWORD1
sta c64.SCRATCH_ZPWORD1
lda IRQ_SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPWORD1+1
lda IRQ_SCRATCH_ZPWORD2
sta c64.SCRATCH_ZPWORD2
lda IRQ_SCRATCH_ZPWORD2+1
sta c64.SCRATCH_ZPWORD2+1
ldx IRQ_X_REG
rts
IRQ_X_REG .byte 0
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPREGX .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
} }
@ -364,7 +402,9 @@ asmsub set_rasterirq(uword rasterpos @ AY) -> clobbers(A) -> () {
rts rts
_raster_irq_handler _raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff lda #$ff
sta c64.VICIRQ ; acknowledge raster irq sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFRT jmp c64.IRQDFRT
@ -403,7 +443,9 @@ asmsub set_rasterirq_excl(uword rasterpos @ AY) -> clobbers(A) -> () {
rts rts
_raster_irq_handler _raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff lda #$ff
sta c64.VICIRQ ; acknowledge raster irq sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFEND ; end irq processing - don't call kernel jmp c64.IRQDFEND ; end irq processing - don't call kernel
@ -480,7 +522,7 @@ _loop sta c64.Colors,y
} }
asmsub scroll_left_full (ubyte alsocolors @ Pc) -> clobbers(A, Y) -> () { asmsub scroll_left_full (ubyte alsocolors @ Pc) -> clobbers(A, Y) -> () {
; ---- scroll the whole screen 1 character to the left ; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself ; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
@ -541,7 +583,7 @@ _scroll_screen ; scroll the screen memory
} }
asmsub scroll_right_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () { asmsub scroll_right_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
; ---- scroll the whole screen 1 character to the right ; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself ; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
@ -594,7 +636,7 @@ _scroll_screen ; scroll the screen memory
} }
asmsub scroll_up_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () { asmsub scroll_up_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
; ---- scroll the whole screen 1 character up ; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself ; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
@ -647,7 +689,7 @@ _scroll_screen ; scroll the screen memory
} }
asmsub scroll_down_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () { asmsub scroll_down_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
; ---- scroll the whole screen 1 character down ; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself ; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
@ -852,7 +894,7 @@ asmsub print_uw0 (uword value @ AY) -> clobbers(A,Y) -> () {
%asm {{ %asm {{
jsr c64utils.uword2decimal jsr c64utils.uword2decimal
ldy #0 ldy #0
- lda c64utils.word2decimal_output,y - lda c64utils.uword2decimal.output,y
jsr c64.CHROUT jsr c64.CHROUT
iny iny
cpy #5 cpy #5
@ -867,25 +909,25 @@ asmsub print_uw (uword value @ AY) -> clobbers(A,Y) -> () {
%asm {{ %asm {{
jsr c64utils.uword2decimal jsr c64utils.uword2decimal
ldy #0 ldy #0
lda c64utils.word2decimal_output lda c64utils.uword2decimal.output
cmp #'0' cmp #'0'
bne _pr_decimal bne _pr_decimal
iny iny
lda c64utils.word2decimal_output+1 lda c64utils.uword2decimal.output+1
cmp #'0' cmp #'0'
bne _pr_decimal bne _pr_decimal
iny iny
lda c64utils.word2decimal_output+2 lda c64utils.uword2decimal.output+2
cmp #'0' cmp #'0'
bne _pr_decimal bne _pr_decimal
iny iny
lda c64utils.word2decimal_output+3 lda c64utils.uword2decimal.output+3
cmp #'0' cmp #'0'
bne _pr_decimal bne _pr_decimal
iny iny
_pr_decimal _pr_decimal
lda c64utils.word2decimal_output,y lda c64utils.uword2decimal.output,y
jsr c64.CHROUT jsr c64.CHROUT
iny iny
cpy #5 cpy #5
@ -895,7 +937,7 @@ _pr_decimal
} }
asmsub print_w (word value @ AY) -> clobbers(A,Y) -> () { asmsub print_w (word value @ AY) -> clobbers(A,Y) -> () {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0s ; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{ %asm {{
cpy #0 cpy #0
bpl + bpl +
@ -916,7 +958,7 @@ asmsub print_w (word value @ AY) -> clobbers(A,Y) -> () {
} }
asmsub input_chars (uword buffer @ AY) -> clobbers(A) -> (ubyte @ Y) { asmsub input_chars (uword buffer @ AY) -> clobbers(A) -> (ubyte @ Y) {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. ; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel! ; It assumes the keyboard is selected as I/O channel!
%asm {{ %asm {{
@ -1044,7 +1086,7 @@ _colormod sta $ffff ; modified
}} }}
} }
asmsub PLOT (ubyte col @ Y, ubyte row @ A) -> clobbers(A) -> () { asmsub plot (ubyte col @ Y, ubyte row @ A) -> clobbers(A) -> () {
; ---- safe wrapper around PLOT kernel routine, to save the X register. ; ---- safe wrapper around PLOT kernel routine, to save the X register.
%asm {{ %asm {{
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX

View File

@ -649,7 +649,57 @@ func_read_flags .proc
rts rts
.pend .pend
func_sqrt16 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD2+1
stx c64.SCRATCH_ZPREGX
ldy #$00 ; r = 0
ldx #$07
clc ; clear bit 16 of m
_loop
tya
ora _stab-1,x
sta c64.SCRATCH_ZPB1 ; (r asl 8) | (d asl 7)
lda c64.SCRATCH_ZPWORD2+1
bcs _skip0 ; m >= 65536? then t <= m is always true
cmp c64.SCRATCH_ZPB1
bcc _skip1 ; t <= m
_skip0
sbc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPWORD2+1 ; m = m - t
tya
ora _stab,x
tay ; r = r or d
_skip1
asl c64.SCRATCH_ZPWORD2
rol c64.SCRATCH_ZPWORD2+1 ; m = m asl 1
dex
bne _loop
; last iteration
bcs _skip2
sty c64.SCRATCH_ZPB1
lda c64.SCRATCH_ZPWORD2
cmp #$80
lda c64.SCRATCH_ZPWORD2+1
sbc c64.SCRATCH_ZPB1
bcc _skip3
_skip2
iny ; r = r or d (d is 1 here)
_skip3
ldx c64.SCRATCH_ZPREGX
tya
sta c64.ESTACK_LO+1,x
lda #0
sta c64.ESTACK_HI+1,x
rts
_stab .byte $01,$02,$04,$08,$10,$20,$40,$80
.pend
func_sin8 .proc func_sin8 .proc
ldy c64.ESTACK_LO+1,x ldy c64.ESTACK_LO+1,x
lda _sinecos8,y lda _sinecos8,y
@ -1112,8 +1162,7 @@ _gtequ dey
_result_minw .word 0 _result_minw .word 0
.pend .pend
func_strlen .proc
func_len_str .proc
; -- push length of 0-terminated string on stack ; -- push length of 0-terminated string on stack
jsr peek_address jsr peek_address
ldy #0 ldy #0
@ -1126,15 +1175,6 @@ func_len_str .proc
rts rts
.pend .pend
func_len_strp .proc
; -- push length of pascal-string on stack
jsr peek_address
ldy #0
lda (c64.SCRATCH_ZPWORD1),y ; first byte is length
sta c64.ESTACK_LO+1,x
rts
.pend
func_rnd .proc func_rnd .proc
; -- put a random ubyte on the estack ; -- put a random ubyte on the estack
jsr math.randbyte jsr math.randbyte

View File

@ -1 +1 @@
1.4 (beta) 1.5 (beta)

View File

@ -88,7 +88,7 @@ private fun compileMain(args: Array<String>) {
println("Syntax check...") println("Syntax check...")
val heap = HeapValues() val heap = HeapValues()
val time1= measureTimeMillis { val time1= measureTimeMillis {
moduleAst.checkIdentifiers(heap) moduleAst.checkIdentifiers(namespace)
} }
//println(" time1: $time1") //println(" time1: $time1")
val time2 = measureTimeMillis { val time2 = measureTimeMillis {
@ -104,9 +104,7 @@ private fun compileMain(args: Array<String>) {
} }
//println(" time4: $time4") //println(" time4: $time4")
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers(heap) // useful for checking symbol usage later? moduleAst.checkIdentifiers(namespace)
// moduleAst.simplifyExpressions(namespace, heap)
// moduleAst.optimizeStatements(namespace, heap)
if(optimize) { if(optimize) {
// optimize the parse tree // optimize the parse tree
println("Optimizing...") println("Optimizing...")

View File

@ -325,8 +325,11 @@ inline fun <reified T> findParentNode(node: Node): T? {
interface IStatement : Node { interface IStatement : Node {
fun process(processor: IAstProcessor) : IStatement fun process(processor: IAstProcessor) : IStatement
fun makeScopedName(name: String): List<String> { fun makeScopedName(name: String): String {
// this is usually cached in a lazy property on the statement object itself // easy way out is to always return the full scoped name.
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
// and like this, we can cache the name even,
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
val scope = mutableListOf<String>() val scope = mutableListOf<String>()
var statementScope = this.parent var statementScope = this.parent
while(statementScope !is ParentSentinel && statementScope !is Module) { while(statementScope !is ParentSentinel && statementScope !is Module) {
@ -335,8 +338,9 @@ interface IStatement : Node {
} }
statementScope = statementScope.parent statementScope = statementScope.parent
} }
scope.add(name) if(name.isNotEmpty())
return scope scope.add(name)
return scope.joinToString(".")
} }
} }
@ -507,7 +511,6 @@ class Block(override val name: String,
val isInLibrary: Boolean, val isInLibrary: Boolean,
override val position: Position) : IStatement, INameScope { override val position: Position) : IStatement, INameScope {
override lateinit var parent: Node override lateinit var parent: Node
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -547,7 +550,6 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
data class Label(val name: String, override val position: Position) : IStatement { data class Label(val name: String, override val position: Position) : IStatement {
override lateinit var parent: Node override lateinit var parent: Node
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -558,6 +560,8 @@ data class Label(val name: String, override val position: Position) : IStatement
override fun toString(): String { override fun toString(): String {
return "Label(name=$name, pos=$position)" return "Label(name=$name, pos=$position)"
} }
val scopedname: String by lazy { makeScopedName(name) }
} }
@ -685,7 +689,7 @@ class VarDecl(val type: VarDeclType,
override fun process(processor: IAstProcessor) = processor.process(this) override fun process(processor: IAstProcessor) = processor.process(this)
val scopedname: String by lazy { makeScopedName(name).joinToString(".") } val scopedname: String by lazy { makeScopedName(name) }
override fun toString(): String { override fun toString(): String {
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)" return "VarDecl(name=$name, vartype=$type, datatype=$datatype, value=$value, pos=$position)"
@ -1584,7 +1588,7 @@ class AnonymousScope(override var statements: MutableList<IStatement>,
override lateinit var parent: Node override lateinit var parent: Node
init { init {
name = "<<<anonymous-$sequenceNumber>>>" name = "<anon-$sequenceNumber>" // make sure it's an invalid soruce code identifier so user source code can never produce it
sequenceNumber++ sequenceNumber++
} }
@ -1625,7 +1629,7 @@ class Subroutine(override val name: String,
override var statements: MutableList<IStatement>, override var statements: MutableList<IStatement>,
override val position: Position) : IStatement, INameScope { override val position: Position) : IStatement, INameScope {
override lateinit var parent: Node override lateinit var parent: Node
val scopedname: String by lazy { makeScopedName(name).joinToString(".") } val scopedname: String by lazy { makeScopedName(name) }
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -2283,31 +2287,6 @@ private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
} }
internal fun registerSet(asmReturnvaluesRegisters: Iterable<RegisterOrStatusflag>): Set<Register> {
val resultRegisters = mutableSetOf<Register>()
for(x in asmReturnvaluesRegisters) {
when(x.registerOrPair) {
RegisterOrPair.A -> resultRegisters.add(Register.A)
RegisterOrPair.X -> resultRegisters.add(Register.X)
RegisterOrPair.Y -> resultRegisters.add(Register.Y)
RegisterOrPair.AX -> {
resultRegisters.add(Register.A)
resultRegisters.add(Register.X)
}
RegisterOrPair.AY -> {
resultRegisters.add(Register.A)
resultRegisters.add(Register.Y)
}
RegisterOrPair.XY -> {
resultRegisters.add(Register.X)
resultRegisters.add(Register.Y)
}
}
}
return resultRegisters
}
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r") internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
internal fun unescape(str: String, position: Position): String { internal fun unescape(str: String, position: Position): String {

View File

@ -139,9 +139,6 @@ private class AstChecker(private val namespace: INameScope,
if(forLoop.body.isEmpty()) if(forLoop.body.isEmpty())
printWarning("for loop body is empty", forLoop.position) printWarning("for loop body is empty", forLoop.position)
if(forLoop.iterable is LiteralValue)
checkResult.add(SyntaxError("currently not possible to loop over a literal value directly, define it as a variable instead", forLoop.position)) // todo loop over literals (by creating a generated variable)
if(!forLoop.iterable.isIterable(namespace, heap)) { if(!forLoop.iterable.isIterable(namespace, heap)) {
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position)) checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
} else { } else {
@ -358,23 +355,17 @@ private class AstChecker(private val namespace: INameScope,
// assigning from a functioncall COULD return multiple values (from an asm subroutine) // assigning from a functioncall COULD return multiple values (from an asm subroutine)
if(assignment.value is FunctionCall) { if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(namespace) val stmt = (assignment.value as FunctionCall).target.targetStatement(namespace)
if (stmt is Subroutine && stmt.returntypes.size > 1) { if (stmt is Subroutine) {
if (stmt.isAsmSubroutine) { if (stmt.isAsmSubroutine) {
if (stmt.returntypes.size != assignment.targets.size) if (stmt.returntypes.size != assignment.targets.size)
checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position)) checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position))
else { else {
if (assignment.targets.all { it.register != null }) {
val returnRegisters = registerSet(stmt.asmReturnvaluesRegisters)
val targetRegisters = assignment.targets.filter { it.register != null }.map { it.register }.toSet()
if (returnRegisters != targetRegisters)
checkResult.add(ExpressionError("asmsub return registers $returnRegisters don't match assignment target registers", assignment.position))
}
for (thing in stmt.returntypes.zip(assignment.targets)) { for (thing in stmt.returntypes.zip(assignment.targets)) {
if (thing.second.determineDatatype(namespace, heap, assignment) != thing.first) if (thing.second.determineDatatype(namespace, heap, assignment) != thing.first)
checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position)) checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position))
} }
} }
} else } else if(assignment.targets.size>1)
checkResult.add(ExpressionError("only asmsub subroutines can return multiple values", assignment.value.position)) checkResult.add(ExpressionError("only asmsub subroutines can return multiple values", assignment.value.position))
} }
} }
@ -695,12 +686,23 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(ExpressionError("remainder can only be used on unsigned integer operands", expr.right.position)) checkResult.add(ExpressionError("remainder can only be used on unsigned integer operands", expr.right.position))
} }
} }
"and", "or", "xor", "&", "|", "^" -> { "and", "or", "xor" -> {
// only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1)
val rightDt = expr.right.resultingDatatype(namespace, heap)
val leftDt = expr.left.resultingDatatype(namespace, heap)
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
checkResult.add(ExpressionError("logical operator can only be used on boolean operands", expr.right.position))
val constLeft = expr.left.constValue(namespace, heap)
val constRight = expr.right.constValue(namespace, heap)
if(constLeft!=null && constLeft.asIntegerValue !in 0..1 || constRight!=null && constRight.asIntegerValue !in 0..1)
checkResult.add(ExpressionError("const literal argument of logical operator must be boolean (0 or 1)", expr.position))
}
"&", "|", "^" -> {
// only integer numeric operands accepted // only integer numeric operands accepted
val rightDt = expr.right.resultingDatatype(namespace, heap) val rightDt = expr.right.resultingDatatype(namespace, heap)
val leftDt = expr.left.resultingDatatype(namespace, heap) val leftDt = expr.left.resultingDatatype(namespace, heap)
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes) if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
checkResult.add(ExpressionError("logical or bitwise operator can only be used on integer operands", expr.right.position)) checkResult.add(ExpressionError("bitwise operator can only be used on integer operands", expr.right.position))
} }
} }
@ -716,12 +718,6 @@ private class AstChecker(private val namespace: INameScope,
override fun process(typecast: TypecastExpression): IExpression { override fun process(typecast: TypecastExpression): IExpression {
if(typecast.type in IterableDatatypes) if(typecast.type in IterableDatatypes)
checkResult.add(ExpressionError("cannot type cast to string or array type", typecast.position)) checkResult.add(ExpressionError("cannot type cast to string or array type", typecast.position))
val funcTarget = (typecast.expression as? IFunctionCall)?.target?.targetStatement(namespace)
if(funcTarget is Subroutine &&
funcTarget.asmReturnvaluesRegisters.isNotEmpty() &&
funcTarget.asmReturnvaluesRegisters.all { it.stack!=true }) {
checkResult.add(ExpressionError("cannot type cast a call to an asmsub that returns value in register - use a variable to store it first", typecast.position))
}
return super.process(typecast) return super.process(typecast)
} }

View File

@ -1,50 +1,53 @@
package prog8.ast package prog8.ast
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
/** /**
* Checks the validity of all identifiers (no conflicts) * Checks the validity of all identifiers (no conflicts)
* Also builds a list of all (scoped) symbol definitions
* Also makes sure that subroutine's parameters also become local variable decls in the subroutine's scope. * Also makes sure that subroutine's parameters also become local variable decls in the subroutine's scope.
* Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly. * Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly.
*/ */
fun Module.checkIdentifiers(heap: HeapValues): MutableMap<String, IStatement> { fun Module.checkIdentifiers(namespace: INameScope) {
val checker = AstIdentifiersChecker(heap) val checker = AstIdentifiersChecker(namespace)
this.process(checker) this.process(checker)
// add any anonymous variables for heap values that are used, and replace literalvalue by identifierref // add any anonymous variables for heap values that are used,
// and replace an iterable literalvalue by identifierref to new local variable
for (variable in checker.anonymousVariablesFromHeap) { for (variable in checker.anonymousVariablesFromHeap) {
val scope = variable.first.definingScope() val scope = variable.first.definingScope()
scope.statements.add(variable.second) scope.statements.add(variable.second)
val parent = variable.first.parent val parent = variable.first.parent
when { when {
parent is Assignment && parent.value === variable.first -> { parent is Assignment && parent.value === variable.first -> {
val idref = IdentifierReference(listOf("auto_heap_value_${variable.first.heapId}"), variable.first.position) val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent) idref.linkParents(parent)
parent.value = idref parent.value = idref
} }
parent is IFunctionCall -> { parent is IFunctionCall -> {
val parameterPos = parent.arglist.indexOf(variable.first) val parameterPos = parent.arglist.indexOf(variable.first)
val idref = IdentifierReference(listOf("auto_heap_value_${variable.first.heapId}"), variable.first.position) val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent) idref.linkParents(parent)
parent.arglist[parameterPos] = idref parent.arglist[parameterPos] = idref
} }
parent is ForLoop -> {
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.iterable = idref
}
else -> TODO("replace literalvalue by identifierref: $variable (in $parent)") else -> TODO("replace literalvalue by identifierref: $variable (in $parent)")
} }
variable.second.linkParents(scope as Node) variable.second.linkParents(scope as Node)
} }
printErrors(checker.result(), name) printErrors(checker.result(), name)
return checker.symbols
} }
private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor { private class AstIdentifiersChecker(private val namespace: INameScope) : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf() private val checkResult: MutableList<AstException> = mutableListOf()
var symbols: MutableMap<String, IStatement> = mutableMapOf() var blocks: MutableMap<String, Block> = mutableMapOf()
private set private set
fun result(): List<AstException> { fun result(): List<AstException> {
@ -52,17 +55,16 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
} }
private fun nameError(name: String, position: Position, existing: IStatement) { private fun nameError(name: String, position: Position, existing: IStatement) {
checkResult.add(NameError("name conflict '$name', first defined in ${existing.position.file} line ${existing.position.line}", position)) checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
} }
override fun process(block: Block): IStatement { override fun process(block: Block): IStatement {
val scopedName = block.scopedname val existing = blocks[block.name]
val existing = symbols[scopedName] if(existing!=null)
if(existing!=null) {
nameError(block.name, block.position, existing) nameError(block.name, block.position, existing)
} else { else
symbols[scopedName] = block blocks[block.name] = block
}
return super.process(block) return super.process(block)
} }
@ -85,13 +87,10 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
// the builtin functions can't be redefined // the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position)) checkResult.add(NameError("builtin function cannot be redefined", decl.position))
val scopedName = decl.scopedname val existing = namespace.lookup(listOf(decl.name), decl)
val existing = symbols[scopedName] if (existing != null && existing !== decl)
if(existing!=null) {
nameError(decl.name, decl.position, existing) nameError(decl.name, decl.position, existing)
} else {
symbols[scopedName] = decl
}
return super.process(decl) return super.process(decl)
} }
@ -103,13 +102,9 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
if (subroutine.parameters.any { it.name in BuiltinFunctions }) if (subroutine.parameters.any { it.name in BuiltinFunctions })
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position)) checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
val scopedName = subroutine.scopedname val existing = namespace.lookup(listOf(subroutine.name), subroutine)
val existing = symbols[scopedName] if (existing != null && existing !== subroutine)
if (existing != null) {
nameError(subroutine.name, subroutine.position, existing) nameError(subroutine.name, subroutine.position, existing)
} else {
symbols[scopedName] = subroutine
}
// check that there are no local variables that redefine the subroutine's parameters // check that there are no local variables that redefine the subroutine's parameters
val allDefinedNames = subroutine.allLabelsAndVariables() val allDefinedNames = subroutine.allLabelsAndVariables()
@ -146,13 +141,9 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
// the builtin functions can't be redefined // the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position)) checkResult.add(NameError("builtin function cannot be redefined", label.position))
} else { } else {
val scopedName = label.scopedname val existing = namespace.lookup(listOf(label.name), label)
val existing = symbols[scopedName] if (existing != null && existing !== label)
if (existing != null) {
nameError(label.name, label.position, existing) nameError(label.name, label.position, existing)
} else {
symbols[scopedName] = label
}
} }
return super.process(label) return super.process(label)
} }
@ -228,13 +219,16 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
internal val anonymousVariablesFromHeap = mutableSetOf<Pair<LiteralValue, VarDecl>>() internal val anonymousVariablesFromHeap = mutableSetOf<Pair<LiteralValue, VarDecl>>()
override fun process(literalValue: LiteralValue): LiteralValue { override fun process(literalValue: LiteralValue): LiteralValue {
if(literalValue.heapId!=null && literalValue.parent !is VarDecl) { if(literalValue.heapId!=null && literalValue.parent !is VarDecl) {
// a literal value that's not declared as a variable, which refers to something on the heap. // a literal value that's not declared as a variable, which refers to something on the heap.
// we need to introduce an auto-generated variable for this to be able to refer to the value! // we need to introduce an auto-generated variable for this to be able to refer to the value!
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "auto_heap_value_${literalValue.heapId}", literalValue, literalValue.position) val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue, literalValue.position)
anonymousVariablesFromHeap.add(Pair(literalValue, variable)) anonymousVariablesFromHeap.add(Pair(literalValue, variable))
} }
return super.process(literalValue) return super.process(literalValue)
} }
} }
private const val autoHeapValuePrefix = "auto_heap_value_"

View File

@ -233,7 +233,7 @@ private class VarInitValueCreator: IAstProcessor {
val scope = decl.definingScope() val scope = decl.definingScope()
if(scope !in vardeclsToAdd) if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf() vardeclsToAdd[scope] = mutableListOf()
vardeclsToAdd[scope]!!.add(decl.asDefaultValueDecl(null)) vardeclsToAdd.getValue(scope).add(decl.asDefaultValueDecl(null))
val declvalue = decl.value!! val declvalue = decl.value!!
val value = val value =
if(declvalue is LiteralValue) { if(declvalue is LiteralValue) {

View File

@ -157,9 +157,8 @@ internal class Compiler(private val rootModule: Module,
} }
override fun process(block: Block): IStatement { override fun process(block: Block): IStatement {
prog.newBlock(block.scopedname, block.name, block.address, block.options()) prog.newBlock(block.name, block.address, block.options())
processVariables(block) processVariables(block)
prog.label("block."+block.scopedname, false)
prog.line(block.position) prog.line(block.position)
translate(block.statements) translate(block.statements)
return super.process(block) return super.process(block)
@ -377,7 +376,16 @@ internal class Compiler(private val rootModule: Module,
} }
private fun translate(stmt: InlineAssembly) { private fun translate(stmt: InlineAssembly) {
prog.instr(Opcode.INLINE_ASSEMBLY, callLabel = stmt.assembly) // If the inline assembly is the only statement inside a subroutine (except vardecls),
// we can use the name of that subroutine to identify it.
// The compiler could then convert it to a special system call
val sub = stmt.parent as? Subroutine
val scopename =
if(sub!=null && sub.statements.filter{it !is VarDecl}.size==1)
sub.scopedname
else
null
prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=scopename, callLabel2 = stmt.assembly)
} }
private fun translate(stmt: Continue) { private fun translate(stmt: Continue) {
@ -452,8 +460,8 @@ internal class Compiler(private val rootModule: Module,
} }
} else { } else {
// regular if..else branching // regular if..else branching
val labelElse = makeLabel("else") val labelElse = makeLabel(branch, "else")
val labelEnd = makeLabel("end") val labelEnd = makeLabel(branch, "end")
val opcode = branchOpcode(branch, true) val opcode = branchOpcode(branch, true)
if (branch.elsepart.isEmpty()) { if (branch.elsepart.isEmpty()) {
prog.instr(opcode, callLabel = labelEnd) prog.instr(opcode, callLabel = labelEnd)
@ -471,9 +479,9 @@ internal class Compiler(private val rootModule: Module,
} }
} }
private fun makeLabel(postfix: String): String { private fun makeLabel(scopeStmt: IStatement, postfix: String): String {
generatedLabelSequenceNumber++ generatedLabelSequenceNumber++
return "_prog8stmt_${generatedLabelSequenceNumber}_$postfix" return "${scopeStmt.makeScopedName("")}.<s-$generatedLabelSequenceNumber-$postfix>"
} }
private fun translate(stmt: IfStatement) { private fun translate(stmt: IfStatement) {
@ -519,13 +527,13 @@ internal class Compiler(private val rootModule: Module,
in WordDatatypes -> Opcode.JZW in WordDatatypes -> Opcode.JZW
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt") else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
} }
val labelEnd = makeLabel("end") val labelEnd = makeLabel(stmt, "end")
if(stmt.elsepart.isEmpty()) { if(stmt.elsepart.isEmpty()) {
prog.instr(conditionJumpOpcode, callLabel = labelEnd) prog.instr(conditionJumpOpcode, callLabel = labelEnd)
translate(stmt.truepart) translate(stmt.truepart)
prog.label(labelEnd) prog.label(labelEnd)
} else { } else {
val labelElse = makeLabel("else") val labelElse = makeLabel(stmt, "else")
prog.instr(conditionJumpOpcode, callLabel = labelElse) prog.instr(conditionJumpOpcode, callLabel = labelElse)
translate(stmt.truepart) translate(stmt.truepart)
prog.instr(Opcode.JUMP, callLabel = labelEnd) prog.instr(Opcode.JUMP, callLabel = labelEnd)
@ -635,10 +643,8 @@ internal class Compiler(private val rootModule: Module,
val funcname = expr.target.nameInSource[0] val funcname = expr.target.nameInSource[0]
translateBuiltinFunctionCall(funcname, expr.arglist) translateBuiltinFunctionCall(funcname, expr.arglist)
} else { } else {
when(target) { if (target is Subroutine) translateSubroutineCall(target, expr.arglist, expr.position)
is Subroutine -> translateSubroutineCall(target, expr.arglist, expr.position) else TODO("non-builtin-function call to $target")
else -> TODO("non-builtin-function call to $target")
}
} }
} }
is IdentifierReference -> translate(expr) is IdentifierReference -> translate(expr)
@ -661,7 +667,7 @@ internal class Compiler(private val rootModule: Module,
in ArrayDatatypes -> { in ArrayDatatypes -> {
if(lv.heapId==null) if(lv.heapId==null)
throw CompilerException("array should have been moved into heap ${lv.position}") throw CompilerException("array should have been moved into heap ${lv.position}")
TODO("push address of array with PUSH_WORD") TODO("push address of array with PUSH_ADDR_HEAPVAR")
} }
else -> throw CompilerException("weird datatype") else -> throw CompilerException("weird datatype")
} }
@ -1003,12 +1009,20 @@ internal class Compiler(private val rootModule: Module,
// the result values of the asm-subroutine that are returned in registers, have to be pushed on the stack // the result values of the asm-subroutine that are returned in registers, have to be pushed on the stack
// (in reversed order) otherwise the asm-subroutine can't be used in expressions. // (in reversed order) otherwise the asm-subroutine can't be used in expressions.
for(rv in subroutine.asmReturnvaluesRegisters.reversed()) { for(rv in subroutine.asmReturnvaluesRegisters.reversed()) {
if(rv.statusflag!=null) if(rv.statusflag!=null) {
TODO("not yet supported: return values in cpu status flag $rv $subroutine") if (rv.statusflag == Statusflag.Pc) {
when(rv.registerOrPair) { prog.instr(Opcode.CARRY_TO_A)
A,X,Y -> prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = rv.registerOrPair.name) prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = Register.A.name)
AX, AY, XY -> prog.instr(Opcode.PUSH_VAR_WORD, callLabel = rv.registerOrPair.name) }
null -> {} else TODO("return value in cpu status flag only supports Carry, not $rv ($subroutine)")
} else {
when (rv.registerOrPair) {
A, X, Y -> prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = rv.registerOrPair.name)
AX -> prog.instr(Opcode.PUSH_REGAX_WORD)
AY -> prog.instr(Opcode.PUSH_REGAY_WORD)
XY -> prog.instr(Opcode.PUSH_REGXY_WORD)
null -> {}
}
} }
} }
} }
@ -1575,74 +1589,17 @@ internal class Compiler(private val rootModule: Module,
private fun translateMultiReturnAssignment(stmt: Assignment) { private fun translateMultiReturnAssignment(stmt: Assignment) {
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(namespace) val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(namespace)
if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) { if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) {
// TODO check correctness of multi-return values (they should be on the stack rather than directly assigned!) // this is the only case where multiple assignment targets are allowed: a call to an asmsub with multiple return values
// we're dealing with the one case where multiple assignment targets are allowed: a call to an asmsub with multiple return values // the return values are already on the stack (the subroutine call puts them there)
// for now, we only support multiple return values as long as they're returned in registers as well. if(stmt.targets.size!=targetStmt.asmReturnvaluesRegisters.size)
if(targetStmt.asmReturnvaluesRegisters.isEmpty()) throw CompilerException("asmsub number of return values doesn't match number of assignment targets ${stmt.position}")
throw CompilerException("we only support multiple return values / assignment when the asmsub returns values in registers") for(target in stmt.targets) {
// if the result registers are not assigned in the exact same registers, or in variables, we need some code val dt = target.determineDatatype(namespace, heap, stmt)
if(stmt.targets.all{it.register!=null}) { popValueIntoTarget(target, dt!!)
val resultRegisters = registerSet(targetStmt.asmReturnvaluesRegisters)
if(stmt.targets.size!=resultRegisters.size)
throw CompilerException("asmsub number of return values doesn't match number of assignment targets ${stmt.position}")
val targetRegs = stmt.targets.filter {it.register!=null}.map{it.register}.toSet()
if(resultRegisters!=targetRegs)
throw CompilerException("asmsub return registers don't match assignment target registers ${stmt.position}")
// output is in registers already, no need to emit any asm code
} else {
// output is in registers but has to be stored somewhere
for(result in targetStmt.asmReturnvaluesRegisters.zip(stmt.targets))
storeRegisterIntoTarget(result.first, result.second, stmt)
} }
} else throw CompilerException("can only use multiple assignment targets on an asmsub call") } else throw CompilerException("can only use multiple assignment targets on an asmsub call")
} }
private fun storeRegisterIntoTarget(registerOrStatus: RegisterOrStatusflag, target: AssignTarget, parent: IStatement) {
if(registerOrStatus.statusflag!=null)
return
when(registerOrStatus.registerOrPair){
A -> {
val assignment = Assignment(listOf(target), null, RegisterExpr(Register.A, target.position), target.position)
assignment.linkParents(parent)
translate(assignment)
}
X -> {
val assignment = Assignment(listOf(target), null, RegisterExpr(Register.X, target.position), target.position)
assignment.linkParents(parent)
translate(assignment)
}
Y -> {
val assignment = Assignment(listOf(target), null, RegisterExpr(Register.Y, target.position), target.position)
assignment.linkParents(parent)
translate(assignment)
}
AX -> {
// deal with register pair AX: target = A + X*256
val targetDt = target.determineDatatype(namespace, heap, parent)
if(targetDt!=DataType.UWORD && targetDt!=DataType.WORD)
throw CompilerException("invalid target datatype for registerpair $targetDt")
prog.instr(Opcode.PUSH_REGAX_WORD)
popValueIntoTarget(target, targetDt)
}
AY -> {
// deal with register pair AY: target = A + Y*256
val targetDt = target.determineDatatype(namespace, heap, parent)
if(targetDt!=DataType.UWORD && targetDt!=DataType.WORD)
throw CompilerException("invalid target datatype for registerpair $targetDt")
prog.instr(Opcode.PUSH_REGAY_WORD)
popValueIntoTarget(target, targetDt)
}
XY -> {
// deal with register pair XY: target = X + Y*256
val targetDt = target.determineDatatype(namespace, heap, parent)
if(targetDt!=DataType.UWORD && targetDt!=DataType.WORD)
throw CompilerException("invalid target datatype for registerpair $targetDt")
prog.instr(Opcode.PUSH_REGXY_WORD)
popValueIntoTarget(target, targetDt)
}
}
}
private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) { private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) {
when { when {
assignTarget.identifier != null -> { assignTarget.identifier != null -> {
@ -1806,9 +1763,9 @@ internal class Compiler(private val rootModule: Module,
* break: * break:
* nop * nop
*/ */
val loopLabel = makeLabel("loop") val loopLabel = makeLabel(loop, "loop")
val continueLabel = makeLabel("continue") val continueLabel = makeLabel(loop, "continue")
val breakLabel = makeLabel("break") val breakLabel = makeLabel(loop, "break")
val indexVarType = if (numElements <= 255) DataType.UBYTE else DataType.UWORD val indexVarType = if (numElements <= 255) DataType.UBYTE else DataType.UWORD
val indexVar = loop.body.getLabelOrVariable(ForLoop.iteratorLoopcounterVarname) as VarDecl val indexVar = loop.body.getLabelOrVariable(ForLoop.iteratorLoopcounterVarname) as VarDecl
@ -1867,9 +1824,9 @@ internal class Compiler(private val rootModule: Module,
* break: * break:
* nop * nop
*/ */
val loopLabel = makeLabel("loop") val loopLabel = makeLabel(body, "loop")
val continueLabel = makeLabel("continue") val continueLabel = makeLabel(body, "continue")
val breakLabel = makeLabel("break") val breakLabel = makeLabel(body, "break")
continueStmtLabelStack.push(continueLabel) continueStmtLabelStack.push(continueLabel)
breakStmtLabelStack.push(breakLabel) breakStmtLabelStack.push(breakLabel)
@ -1972,9 +1929,9 @@ internal class Compiler(private val rootModule: Module,
startAssignment.linkParents(body) startAssignment.linkParents(body)
translate(startAssignment) translate(startAssignment)
val loopLabel = makeLabel("loop") val loopLabel = makeLabel(body, "loop")
val continueLabel = makeLabel("continue") val continueLabel = makeLabel(body, "continue")
val breakLabel = makeLabel("break") val breakLabel = makeLabel(body, "break")
val literalStepValue = (range.step as? LiteralValue)?.asNumericValue?.toInt() val literalStepValue = (range.step as? LiteralValue)?.asNumericValue?.toInt()
continueStmtLabelStack.push(continueLabel) continueStmtLabelStack.push(continueLabel)
@ -2076,9 +2033,9 @@ internal class Compiler(private val rootModule: Module,
* break: * break:
* nop * nop
*/ */
val loopLabel = makeLabel("loop") val loopLabel = makeLabel(stmt, "loop")
val breakLabel = makeLabel("break") val breakLabel = makeLabel(stmt, "break")
val continueLabel = makeLabel("continue") val continueLabel = makeLabel(stmt, "continue")
prog.line(stmt.position) prog.line(stmt.position)
breakStmtLabelStack.push(breakLabel) breakStmtLabelStack.push(breakLabel)
continueStmtLabelStack.push(continueLabel) continueStmtLabelStack.push(continueLabel)
@ -2114,9 +2071,9 @@ internal class Compiler(private val rootModule: Module,
* break: * break:
* nop * nop
*/ */
val loopLabel = makeLabel("loop") val loopLabel = makeLabel(stmt, "loop")
val continueLabel = makeLabel("continue") val continueLabel = makeLabel(stmt, "continue")
val breakLabel = makeLabel("break") val breakLabel = makeLabel(stmt, "break")
prog.line(stmt.position) prog.line(stmt.position)
breakStmtLabelStack.push(breakLabel) breakStmtLabelStack.push(breakLabel)
continueStmtLabelStack.push(continueLabel) continueStmtLabelStack.push(continueLabel)
@ -2137,13 +2094,6 @@ internal class Compiler(private val rootModule: Module,
} }
private fun translate(expr: TypecastExpression) { private fun translate(expr: TypecastExpression) {
val funcTarget = (expr.expression as? IFunctionCall)?.target?.targetStatement(namespace)
if(funcTarget is Subroutine &&
funcTarget.asmReturnvaluesRegisters.isNotEmpty() &&
funcTarget.asmReturnvaluesRegisters.all { it.stack!=true }) {
throw CompilerException("cannot type cast a call to an asmsub that returns value in register - use a variable to store it first")
}
translate(expr.expression) translate(expr.expression)
val sourceDt = expr.expression.resultingDatatype(namespace, heap) ?: throw CompilerException("don't know what type to cast") val sourceDt = expr.expression.resultingDatatype(namespace, heap) ?: throw CompilerException("don't know what type to cast")
if(sourceDt==expr.type) if(sourceDt==expr.type)
@ -2233,7 +2183,7 @@ internal class Compiler(private val rootModule: Module,
File(filename).readText() File(filename).readText()
} }
prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=scopeprefix+sourcecode+scopeprefixEnd) prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=null, callLabel2=scopeprefix+sourcecode+scopeprefixEnd)
} }
private fun translateAsmBinary(args: List<DirectiveArg>) { private fun translateAsmBinary(args: List<DirectiveArg>) {

View File

@ -8,15 +8,21 @@ open class Instruction(val opcode: Opcode,
val callLabel: String? = null, val callLabel: String? = null,
val callLabel2: String? = null) val callLabel2: String? = null)
{ {
lateinit var next: Instruction var branchAddress: Int? = null
var nextAlt: Instruction? = null
override fun toString(): String { override fun toString(): String {
val argStr = arg?.toString() ?: "" val argStr = arg?.toString() ?: ""
val result = val result =
when { when {
opcode==Opcode.LINE -> "_line $callLabel" opcode==Opcode.LINE -> "_line $callLabel"
opcode==Opcode.INLINE_ASSEMBLY -> "inline_assembly" opcode==Opcode.INLINE_ASSEMBLY -> {
// inline assembly is not written out (it can't be processed as intermediate language)
// instead, it is converted into a system call that can be intercepted by the vm
if(callLabel!=null)
"syscall SYSASM.$callLabel\n return"
else
"inline_assembly"
}
opcode==Opcode.SYSCALL -> { opcode==Opcode.SYSCALL -> {
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() } val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
"syscall $syscall" "syscall $syscall"

View File

@ -11,8 +11,7 @@ import java.nio.file.Path
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val importedFrom: Path) { class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val importedFrom: Path) {
class ProgramBlock(val scopedname: String, class ProgramBlock(val name: String,
val shortname: String,
var address: Int?, var address: Int?,
val instructions: MutableList<Instruction> = mutableListOf(), val instructions: MutableList<Instruction> = mutableListOf(),
val variables: MutableMap<String, Value> = mutableMapOf(), val variables: MutableMap<String, Value> = mutableMapOf(),
@ -447,8 +446,8 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
currentBlock.memoryPointers[name] = Pair(address, datatype) currentBlock.memoryPointers[name] = Pair(address, datatype)
} }
fun newBlock(scopedname: String, shortname: String, address: Int?, options: Set<String>) { fun newBlock(name: String, address: Int?, options: Set<String>) {
currentBlock = ProgramBlock(scopedname, shortname, address, force_output="force_output" in options) currentBlock = ProgramBlock(name, address, force_output="force_output" in options)
blocks.add(currentBlock) blocks.add(currentBlock)
} }
@ -472,7 +471,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
} }
out.println("%end_heap") out.println("%end_heap")
for(blk in blocks) { for(blk in blocks) {
out.println("\n%block ${blk.scopedname} ${blk.address?.toString(16) ?: ""}") out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
out.println("%variables") out.println("%variables")
for(variable in blk.variables) { for(variable in blk.variables) {

View File

@ -244,7 +244,6 @@ enum class Opcode {
JZW, // branch if value is zero (word) JZW, // branch if value is zero (word)
JNZW, // branch if value is not zero (word) JNZW, // branch if value is not zero (word)
// subroutines // subroutines
CALL, CALL,
RETURN, RETURN,
@ -257,6 +256,7 @@ enum class Opcode {
CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
SEI, // set irq-disable status flag SEI, // set irq-disable status flag
CLI, // clear irq-disable status flag CLI, // clear irq-disable status flag
CARRY_TO_A, // load var/register A with carry status bit
RSAVE, // save all internal registers and status flags RSAVE, // save all internal registers and status flags
RSAVEX, // save just X (the evaluation stack pointer) RSAVEX, // save just X (the evaluation stack pointer)
RRESTORE, // restore all internal registers and status flags RRESTORE, // restore all internal registers and status flags

View File

@ -23,10 +23,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
private var breakpointCounter = 0 private var breakpointCounter = 0
init { init {
// Because 64tass understands scoped names via .proc / .block, // Convert invalid label names (such as "<anon-1>") to something that's allowed.
// we'll strip the block prefix from all scoped names in the program.
// Also, convert invalid label names (such as "<<<anonymous-1>>>") to something that's allowed.
// Also have to do that for the variablesMarkedForZeropage!
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>() val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
for(block in program.blocks) { for(block in program.blocks) {
val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap() val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
@ -42,14 +39,13 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
callLabel2 = if (it.callLabel2 != null) symname(it.callLabel2, block) else null) callLabel2 = if (it.callLabel2 != null) symname(it.callLabel2, block) else null)
} }
}.toMutableList() }.toMutableList()
val newConstants = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap() val newMempointers = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
val newblock = IntermediateProgram.ProgramBlock( val newblock = IntermediateProgram.ProgramBlock(
block.scopedname, block.name,
block.shortname,
block.address, block.address,
newinstructions, newinstructions,
newvars, newvars,
newConstants, newMempointers,
newlabels, newlabels,
force_output = block.force_output) force_output = block.force_output)
newblock.variablesMarkedForZeropage.clear() newblock.variablesMarkedForZeropage.clear()
@ -106,29 +102,20 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
} }
// convert a fully scoped name (defined in the given block) to a valid assembly symbol name
private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String { private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String {
if(' ' in scoped) if(' ' in scoped)
return scoped return scoped
val blockLocal: Boolean val blockLocal: Boolean
var name = when { var name = if (block!=null && scoped.startsWith("${block.name}.")) {
block==null -> { blockLocal = true
blockLocal=true scoped.substring(block.name.length+1)
scoped
}
scoped.startsWith("${block.shortname}.") -> {
blockLocal = true
scoped.substring(block.shortname.length+1)
}
scoped.startsWith("block.") -> {
blockLocal = false
scoped
}
else -> {
blockLocal = false
scoped
}
} }
name = name.replace("<<<", "prog8_").replace(">>>", "") else {
blockLocal = false
scoped
}
name = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
if(name=="-") if(name=="-")
return "-" return "-"
if(blockLocal) if(blockLocal)
@ -199,7 +186,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
for(block in program.blocks) { for(block in program.blocks) {
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name==initvarsSubName } as? LabelInstr val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name==initvarsSubName } as? LabelInstr
if(initVarsLabel!=null) if(initVarsLabel!=null)
out(" jsr ${block.scopedname}.${initVarsLabel.name}") out(" jsr ${block.name}.${initVarsLabel.name}")
} }
out(" clc") out(" clc")
when(zeropage.exitProgramStrategy) { when(zeropage.exitProgramStrategy) {
@ -222,9 +209,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
private fun block2asm(blk: IntermediateProgram.ProgramBlock) { private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
block = blk block = blk
out("\n; ---- block: '${block.shortname}' ----") out("\n; ---- block: '${block.name}' ----")
if(!blk.force_output) if(!blk.force_output)
out("${block.shortname}\t.proc\n") out("${block.name}\t.proc\n")
if(block.address!=null) { if(block.address!=null) {
out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'") out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'")
out("* = ${block.address?.toHex()}") out("* = ${block.address?.toHex()}")
@ -232,14 +219,14 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
// deal with zeropage variables // deal with zeropage variables
for(variable in blk.variables) { for(variable in blk.variables) {
val sym = symname(blk.scopedname+"."+variable.key, null) val sym = symname(blk.name+"."+variable.key, null)
val zpVar = program.allocatedZeropageVariables[sym] val zpVar = program.allocatedZeropageVariables[sym]
if(zpVar==null) { if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space) // This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) { if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) {
try { try {
val address = zeropage.allocate(sym, variable.value.type, null) val address = zeropage.allocate(sym, variable.value.type, null)
out("${variable.key} = $address\t; zp ${variable.value.type}") out("${variable.key} = $address\t; auto zp ${variable.value.type}")
// make sure we add the var to the set of zpvars for this block // make sure we add the var to the set of zpvars for this block
blk.variablesMarkedForZeropage.add(variable.key) blk.variablesMarkedForZeropage.add(variable.key)
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type) program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
@ -296,7 +283,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
DataType.STR, DataType.STR_S -> { DataType.STR, DataType.STR_S -> {
val rawStr = heap.get(v.second.heapId).str!! val rawStr = heap.get(v.second.heapId).str!!
val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') } val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') }
out("${v.first}\t; ${v.second.type} \"${escape(rawStr)}\"") out("${v.first}\t; ${v.second.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
for (chunk in bytes.chunked(16)) for (chunk in bytes.chunked(16))
out(" .byte " + chunk.joinToString()) out(" .byte " + chunk.joinToString())
} }
@ -427,12 +414,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
private fun simpleInstr2Asm(ins: Instruction): String? { private fun simpleInstr2Asm(ins: Instruction): String? {
// a label 'instruction' is simply translated into a asm label // a label 'instruction' is simply translated into a asm label
if(ins is LabelInstr) { if(ins is LabelInstr) {
if(ins.name.startsWith("block."))
return ""
val labelresult = val labelresult =
if(ins.name.startsWith("${block.shortname}.")) if(ins.name.startsWith("${block.name}."))
ins.name.substring(block.shortname.length+1) ins.name.substring(block.name.length+1)
else else
ins.name ins.name
return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult
@ -449,6 +433,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.CLC -> " clc" Opcode.CLC -> " clc"
Opcode.SEI -> " sei" Opcode.SEI -> " sei"
Opcode.CLI -> " cli" Opcode.CLI -> " cli"
Opcode.CARRY_TO_A -> " lda #0 | adc #0"
Opcode.JUMP -> { Opcode.JUMP -> {
if(ins.callLabel!=null) if(ins.callLabel!=null)
" jmp ${ins.callLabel}" " jmp ${ins.callLabel}"
@ -476,7 +461,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.DISCARD_BYTE -> " inx" Opcode.DISCARD_BYTE -> " inx"
Opcode.DISCARD_WORD -> " inx" Opcode.DISCARD_WORD -> " inx"
Opcode.DISCARD_FLOAT -> " inx | inx | inx" Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel ?: "") // All of the inline assembly is stored in the calllabel property. the '@inline@' is a special marker to process it. Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to process it.
Opcode.SYSCALL -> { Opcode.SYSCALL -> {
if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr }) if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr })
throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}") throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}")
@ -1255,9 +1240,6 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.ROR2_WORD -> { Opcode.ROR2_WORD -> {
AsmFragment(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+", 30) AsmFragment(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+", 30)
} }
// Opcode.SYSCALL -> {
// TODO("optimize SYSCALL $ins in-place on variable $variable")
// }
else -> null else -> null
} }
} }

View File

@ -1055,11 +1055,12 @@ class Petscii {
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
return text.map { return text.map {
val petscii = lookup[it] val petscii = lookup[it]
if(petscii==null) { petscii?.toShort() ?: if(it=='\u0000')
val case = if(lowercase) "lower" else "upper" 0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}case Petscii character for '$it'") throw CharConversionException("no ${case}case Petscii character for '$it'")
} }
petscii.toShort()
} }
} }
@ -1072,11 +1073,12 @@ class Petscii {
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
return text.map{ return text.map{
val screencode = lookup[it] val screencode = lookup[it]
if(screencode==null) { screencode?.toShort() ?: if(it=='\u0000')
val case = if(lowercase) "lower" else "upper" 0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it'") throw CharConversionException("no ${case}Screencode character for '$it'")
} }
screencode.toShort()
} }
} }

View File

@ -46,7 +46,7 @@ val BuiltinFunctions = mapOf(
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::atan) }, "atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::atan) },
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::log) }, "ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::log) },
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, ::log2) }, "log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, ::log2) },
// TODO: sqrt() should have integer versions too "sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { Math.sqrt(it.toDouble()).toInt() } },
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sqrt) }, "sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toRadians) }, "rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toDegrees) }, "deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toDegrees) },
@ -84,6 +84,7 @@ val BuiltinFunctions = mapOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)), BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)), BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null), BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", StringDatatypes)), DataType.UBYTE, ::builtinStrlen),
"vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null), "vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null), "vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null), "vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null),
@ -302,6 +303,20 @@ private fun builtinAvg(args: List<IExpression>, position: Position, namespace:IN
return numericLiteral(result, args[0].position) return numericLiteral(result, args[0].position)
} }
private fun builtinStrlen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
val argument = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
if(argument.type !in StringDatatypes)
throw SyntaxError("strlen must have string argument", position)
val string = argument.strvalue(heap)
val zeroIdx = string.indexOf('\u0000')
return if(zeroIdx>=0)
LiteralValue.optimalInteger(zeroIdx, position=position)
else
LiteralValue.optimalInteger(string.length, position=position)
}
private fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE. // note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1) if(args.size!=1)

View File

@ -11,7 +11,7 @@ class Program (val name: String,
val program: MutableList<Instruction>, val program: MutableList<Instruction>,
val variables: Map<String, Value>, val variables: Map<String, Value>,
val memoryPointers: Map<String, Pair<Int, DataType>>, val memoryPointers: Map<String, Pair<Int, DataType>>,
val labels: Map<String, Instruction>, val labels: Map<String, Int>,
val memory: Map<Int, List<Value>>, val memory: Map<Int, List<Value>>,
val heap: HeapValues) val heap: HeapValues)
{ {
@ -20,7 +20,6 @@ class Program (val name: String,
program.add(LabelInstr("____program_end", false)) program.add(LabelInstr("____program_end", false))
program.add(Instruction(Opcode.TERMINATE)) program.add(Instruction(Opcode.TERMINATE))
program.add(Instruction(Opcode.NOP)) program.add(Instruction(Opcode.NOP))
connect()
} }
companion object { companion object {
@ -31,7 +30,7 @@ class Program (val name: String,
val program = mutableListOf<Instruction>() val program = mutableListOf<Instruction>()
val variables = mutableMapOf<String, Value>() val variables = mutableMapOf<String, Value>()
val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>() val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>()
val labels = mutableMapOf<String, Instruction>() val labels = mutableMapOf<String, Int>()
while(lines.hasNext()) { while(lines.hasNext()) {
val (lineNr, line) = lines.next() val (lineNr, line) = lines.next()
@ -53,7 +52,7 @@ class Program (val name: String,
program: MutableList<Instruction>, program: MutableList<Instruction>,
variables: MutableMap<String, Value>, variables: MutableMap<String, Value>,
memoryPointers: MutableMap<String, Pair<Int, DataType>>, memoryPointers: MutableMap<String, Pair<Int, DataType>>,
labels: MutableMap<String, Instruction>) labels: MutableMap<String, Int>)
{ {
while(true) { while(true) {
val (_, line) = lines.next() val (_, line) = lines.next()
@ -67,8 +66,10 @@ class Program (val name: String,
loadMemoryPointers(lines, memoryPointers, heap) loadMemoryPointers(lines, memoryPointers, heap)
else if(line=="%instructions") { else if(line=="%instructions") {
val (blockInstructions, blockLabels) = loadInstructions(lines, heap) val (blockInstructions, blockLabels) = loadInstructions(lines, heap)
val baseIndex = program.size
program.addAll(blockInstructions) program.addAll(blockInstructions)
labels.putAll(blockLabels) val labelsWithIndex = blockLabels.mapValues { baseIndex+blockInstructions.indexOf(it.value) }
labels.putAll(labelsWithIndex)
} }
} }
} }
@ -142,8 +143,19 @@ class Program (val name: String,
Instruction(opcode, callLabel = withoutQuotes) Instruction(opcode, callLabel = withoutQuotes)
} }
Opcode.SYSCALL -> { Opcode.SYSCALL -> {
val call = Syscall.valueOf(args!!) if(args!! in syscallNames) {
Instruction(opcode, Value(DataType.UBYTE, call.callNr)) val call = Syscall.valueOf(args)
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
} else {
val args2 = args.replace('.', '_')
if(args2 in syscallNames) {
val call = Syscall.valueOf(args2)
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
} else {
// the syscall is not yet implemented. emit a stub.
Instruction(Opcode.SYSCALL, Value(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
}
}
} }
else -> { else -> {
Instruction(opcode, getArgValue(args, heap)) Instruction(opcode, getArgValue(args, heap))
@ -261,50 +273,4 @@ class Program (val name: String,
} }
} }
} }
private fun connect() {
val it1 = program.iterator()
val it2 = program.iterator()
it2.next()
while(it1.hasNext() && it2.hasNext()) {
val instr = it1.next()
val nextInstr = it2.next()
when(instr.opcode) {
Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction
Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic
Opcode.JUMP -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support JUMP to memory address")
} else {
// jump to label
val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = target
}
}
Opcode.BCC, Opcode.BCS, Opcode.BZ, Opcode.BNZ, Opcode.BNEG, Opcode.BPOS, Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support branch to memory address")
} else {
// branch to label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr
}
}
Opcode.CALL -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support CALL to memory address")
} else {
// call label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr // instruction to return to
}
}
else -> instr.next = nextInstr
}
}
}
} }

View File

@ -15,6 +15,8 @@ class BitmapScreenPanel : KeyListener, JPanel() {
private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB) private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB)
private val g2d = image.graphics as Graphics2D private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0
private var cursorY: Int=0
init { init {
val size = Dimension(image.width * SCALING, image.height * SCALING) val size = Dimension(image.width * SCALING, image.height * SCALING)
@ -48,6 +50,8 @@ class BitmapScreenPanel : KeyListener, JPanel() {
fun clearScreen(color: Int) { fun clearScreen(color: Int) {
g2d.background = palette[color and 15] g2d.background = palette[color and 15]
g2d.clearRect(0, 0, BitmapScreenPanel.SCREENWIDTH, BitmapScreenPanel.SCREENHEIGHT) g2d.clearRect(0, 0, BitmapScreenPanel.SCREENWIDTH, BitmapScreenPanel.SCREENHEIGHT)
cursorX = 0
cursorY = 0
} }
fun setPixel(x: Int, y: Int, color: Int) { fun setPixel(x: Int, y: Int, color: Int) {
image.setRGB(x, y, palette[color and 15].rgb) image.setRGB(x, y, palette[color and 15].rgb)
@ -56,25 +60,74 @@ class BitmapScreenPanel : KeyListener, JPanel() {
g2d.color = palette[color and 15] g2d.color = palette[color and 15]
g2d.drawLine(x1, y1, x2, y2) g2d.drawLine(x1, y1, x2, y2)
} }
fun writeText(x: Int, y: Int, text: String, color: Int) { fun printText(text: String, color: Int, lowercase: Boolean) {
if(color!=1) { val lines = text.split('\n')
TODO("text can only be white for now") for(line in lines.withIndex()) {
} printTextSingleLine(line.value, color, lowercase)
var xx=x if(line.index<lines.size-1) {
var yy=y cursorX=0
for(sc in Petscii.encodeScreencode(text, true)) { cursorY++
setChar(xx, yy, sc)
xx++
if(xx>=(SCREENWIDTH/8)) {
yy++
xx=0
} }
} }
} }
private fun printTextSingleLine(text: String, color: Int, lowercase: Boolean) {
if(color!=1) {
TODO("text can only be white for now")
}
for(clearx in cursorX until cursorX+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(cursorX, cursorY, sc)
cursorX++
if(cursorX>=(SCREENWIDTH/8)) {
cursorY++
cursorX=0
}
}
}
fun printChar(char: Short) {
if(char==13.toShort() || char==141.toShort()) {
cursorX=0
cursorY++
} else {
setChar(cursorX, cursorY, char)
cursorX++
if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++
cursorX = 0
}
}
}
fun setChar(x: Int, y: Int, screenCode: Short) { fun setChar(x: Int, y: Int, screenCode: Short) {
g2d.clearRect(8*x, 8*y, 8, 8)
g2d.drawImage(Charset.shiftedChars[screenCode.toInt()], 8*x, 8*y , null) g2d.drawImage(Charset.shiftedChars[screenCode.toInt()], 8*x, 8*y , null)
} }
fun setCursorPos(x: Int, y: Int) {
cursorX = x
cursorY = y
}
fun getCursorPos(): Pair<Int, Int> {
return Pair(cursorX, cursorY)
}
fun writeText(x: Int, y: Int, text: String, color: Int, lowercase: Boolean) {
var xx=x
if(color!=1) {
TODO("text can only be white for now")
}
for(clearx in xx until xx+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(xx++, y, sc)
}
}
companion object { companion object {
const val SCREENWIDTH = 320 const val SCREENWIDTH = 320

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,7 @@ class TestStackVmOpcodes {
private fun makeProg(ins: MutableList<Instruction>, private fun makeProg(ins: MutableList<Instruction>,
vars: Map<String, Value>?=null, vars: Map<String, Value>?=null,
memoryPointers: Map<String, Pair<Int, DataType>>?=null, memoryPointers: Map<String, Pair<Int, DataType>>?=null,
labels: Map<String, Instruction>?=null, labels: Map<String, Int>?=null,
mem: Map<Int, List<Value>>?=null) : Program { mem: Map<Int, List<Value>>?=null) : Program {
val heap = HeapValues() val heap = HeapValues()
return Program("test", ins, vars ?: mapOf(), memoryPointers ?: mapOf(), labels ?: mapOf(), mem ?: mapOf(), heap) return Program("test", ins, vars ?: mapOf(), memoryPointers ?: mapOf(), labels ?: mapOf(), mem ?: mapOf(), heap)
@ -751,7 +751,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2")) Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
assertEquals("", vm.sourceLine) assertEquals("", vm.sourceLine)
@ -771,7 +771,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2")) Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
assertFalse(vm.P_carry) assertFalse(vm.P_carry)
vm.step(2) vm.step(2)
@ -792,7 +792,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2")) Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
assertEquals("", vm.sourceLine) assertEquals("", vm.sourceLine)
@ -812,7 +812,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2")) Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
assertEquals("", vm.sourceLine) assertEquals("", vm.sourceLine)
@ -832,7 +832,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2")) Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
assertEquals("", vm.sourceLine) assertEquals("", vm.sourceLine)
@ -852,7 +852,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2")) Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
assertEquals("", vm.sourceLine) assertEquals("", vm.sourceLine)
@ -869,7 +869,7 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.LINE, callLabel = "string1"),
Instruction(Opcode.TERMINATE), Instruction(Opcode.TERMINATE),
Instruction(Opcode.LINE, callLabel = "string2")) Instruction(Opcode.LINE, callLabel = "string2"))
val labels = mapOf("label" to ins.last()) // points to the second LINE instruction val labels = mapOf("label" to ins.size-1) // points to the second LINE instruction
vm.load(makeProg(ins, labels=labels), null) vm.load(makeProg(ins, labels=labels), null)
vm.step(2) vm.step(2)
assertEquals("string2", vm.sourceLine) assertEquals("string2", vm.sourceLine)
@ -889,7 +889,7 @@ class TestStackVmOpcodes {
vm.step(1) vm.step(1)
} }
vm.callstack.add(ins[2]) // set the LINE opcode as return instruction vm.callstack.add(2) // set the LINE opcode as return instruction
assertEquals("", vm.sourceLine) assertEquals("", vm.sourceLine)
vm.step(2) vm.step(2)
assertEquals("string1", vm.sourceLine) assertEquals("string1", vm.sourceLine)
@ -906,12 +906,12 @@ class TestStackVmOpcodes {
Instruction(Opcode.LINE, callLabel = "called"), Instruction(Opcode.LINE, callLabel = "called"),
Instruction(Opcode.RETURN) Instruction(Opcode.RETURN)
) )
val labels = mapOf("label" to ins[3]) // points to the LINE instruction val labels = mapOf("label" to 3) // points to the LINE instruction
vm.load(makeProg(ins, labels = labels), null) vm.load(makeProg(ins, labels = labels), null)
vm.step(1) vm.step(1)
assertEquals("", vm.sourceLine) assertEquals("", vm.sourceLine)
assertEquals(1, vm.callstack.size) assertEquals(1, vm.callstack.size)
assertSame(ins[1], vm.callstack.peek()) assertSame(1, vm.callstack.peek())
vm.step(1) vm.step(1)
assertEquals("called", vm.sourceLine) assertEquals("called", vm.sourceLine)
vm.step(1) vm.step(1)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -25,6 +25,18 @@ The project is on github: https://github.com/irmen/prog8.git
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
.. image:: _static/cube3d.png
:width: 33%
:alt: 3d rotating sprites
.. image:: _static/wizzine.png
:width: 33%
:alt: Simple wizzine sprite effect
.. image:: _static/tehtriz.png
:width: 33%
:alt: Fully playable tetris clone
Code example Code example
------------ ------------

View File

@ -604,8 +604,11 @@ ln(x)
log2(x) log2(x)
Base 2 logarithm. Base 2 logarithm.
sqrt16(w)
16 bit unsigned integer Square root. Result is unsigned byte.
sqrt(x) sqrt(x)
Square root. Floating point Square root.
round(x) round(x)
Rounds the floating point to the closest integer. Rounds the floating point to the closest integer.
@ -639,6 +642,11 @@ len(x)
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte.
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
length of the string during execution, the value of len(string) may no longer be correct! length of the string during execution, the value of len(string) may no longer be correct!
(use strlen function if you want to dynamically determine the length)
strlen(str)
Number of bytes in the string. This value is determined during runtime and counts upto
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
lsb(x) lsb(x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte". Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".

View File

@ -362,14 +362,13 @@ Operators
.. todo:: .. todo::
address-of: ``#`` or ``&`` (to stay close to C) address-of: ``#`` or ``&`` (to stay close to C)
Takes the address of the symbol following it: ``word address = &somevar`` Takes the address of the symbol following it: ``word address = &somevar``
Perhaps requires an explicit pointer type as well instead of just word? Perhaps requires an explicit pointer type as well instead of just word?
This can replace the ``memory`` var decl prefix as well, instead of This can replace the ``memory`` var decl prefix as well, instead of
``memory uword var = $c000`` we could write ``&uword var = $c000`` ``memory uword var = $c000`` we could write ``&uword var = $c000``
arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%`` arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%``
``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations. ``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations.
``/`` is division (will result in integer division when using on integer operands, and a floating point division when at least one of the operands is a float) ``/`` is division (will result in integer division when using on integer operands, and a floating point division when at least one of the operands is a float)
@ -436,10 +435,20 @@ You call a subroutine like this::
[ result = ] subroutinename_or_address ( [argument...] ) [ result = ] subroutinename_or_address ( [argument...] )
; example: ; example:
resultvariable = subroutine ( arg1, arg2, arg3 ) resultvariable = subroutine(arg1, arg2, arg3)
Arguments are separated by commas. The argument list can also be empty if the subroutine Arguments are separated by commas. The argument list can also be empty if the subroutine
takes no parameters. takes no parameters. If the subroutine returns a value, you can still omit the assignment to
a result variable (but the compiler will warn you about discarding the result of the call).
Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code or referencing
a routine in kernel ROM) can return more than one return values, for instance a status
in the carry bit and a number in A, or a 16-bit value in A/Y registers.
Only for these kind of subroutines it is possible to write a multi value assignment to
store the resulting values::
var1, var2, var3 = asmsubroutine()

View File

@ -44,10 +44,10 @@ sub start() {
sub print_notes(ubyte n1, ubyte n2) { sub print_notes(ubyte n1, ubyte n2) {
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.PLOT(n1/2, 24) c64scr.plot(n1/2, 24)
c64.COLOR=7 c64.COLOR=7
c64.CHROUT('Q') c64.CHROUT('Q')
c64scr.PLOT(n2/2, 24) c64scr.plot(n2/2, 24)
c64.COLOR=4 c64.COLOR=4
c64.CHROUT('Q') c64.CHROUT('Q')
} }

View File

@ -24,7 +24,7 @@
c64scr.clear_screenchars(32) c64scr.clear_screenchars(32)
draw_edges() draw_edges()
time+=0.2 time+=0.2
c64scr.PLOT(0,0) c64scr.plot(0,0)
c64scr.print("3d cube! (float) ") c64scr.print("3d cube! (float) ")
c64scr.print_ub(c64.TIME_LO) c64scr.print_ub(c64.TIME_LO)
c64scr.print(" jiffies/frame") c64scr.print(" jiffies/frame")

View File

@ -89,7 +89,7 @@
anglex-=500 anglex-=500
angley+=217 angley+=217
anglez+=452 anglez+=452
c64scr.PLOT(0,0) c64scr.plot(0,0)
c64scr.print("3d cube! (sprites) ") c64scr.print("3d cube! (sprites) ")
c64scr.print_ub(c64.TIME_LO) c64scr.print_ub(c64.TIME_LO)
c64scr.print(" jiffies/frame ") c64scr.print(" jiffies/frame ")

View File

@ -29,7 +29,7 @@
anglex+=1000 anglex+=1000
angley+=433 angley+=433
anglez+=907 anglez+=907
c64scr.PLOT(0,0) c64scr.plot(0,0)
c64scr.print("3d cube! (integer) ") c64scr.print("3d cube! (integer) ")
c64scr.print_ub(c64.TIME_LO) c64scr.print_ub(c64.TIME_LO)
c64scr.print(" jiffies/frame") c64scr.print(" jiffies/frame")

View File

@ -32,7 +32,6 @@
float minutes = floor(clock_seconds / 60) float minutes = floor(clock_seconds / 60)
clock_seconds = floor(clock_seconds - minutes * 60.0) clock_seconds = floor(clock_seconds - minutes * 60.0)
; @todo implement strcpy/strcat/strlen?
c64scr.print("system time in ti$ is ") c64scr.print("system time in ti$ is ")
c64flt.print_f(hours) c64flt.print_f(hours)
c64.CHROUT(':') c64.CHROUT(':')

View File

@ -1,18 +0,0 @@
~ main {
sub start() {
if A>10 {
A=44
while true {
;derp
}
} else {
gameover:
goto gameover
}
}
}

View File

@ -10,7 +10,7 @@
const ubyte max_iter = 16 const ubyte max_iter = 16
sub start() { sub start() {
c64scr.print("calculating mandelbrot fractal...\n") c64scr.print("calculating mandelbrot fractal...")
c64.TIME_HI=0 c64.TIME_HI=0
c64.TIME_MID=0 c64.TIME_MID=0
@ -42,7 +42,7 @@
float duration = floor(((c64.TIME_LO as float) float duration = floor(((c64.TIME_LO as float)
+ 256.0*(c64.TIME_MID as float) + 256.0*(c64.TIME_MID as float)
+ 65536.0*(c64.TIME_HI as float))/60.0) + 65536.0*(c64.TIME_HI as float))/60.0)
c64scr.PLOT(0, 21) c64scr.plot(0, 21)
c64scr.print("finished in ") c64scr.print("finished in ")
c64flt.print_f(duration) c64flt.print_f(duration)
c64scr.print(" seconds!\n") c64scr.print(" seconds!\n")

View File

@ -29,7 +29,7 @@
c64scr.print("es") c64scr.print("es")
c64scr.print(" left.\nWhat is your next guess? ") c64scr.print(" left.\nWhat is your next guess? ")
c64scr.input_chars(input) c64scr.input_chars(input)
ubyte guess = c64utils.str2ubyte(input) ubyte guess = lsb(c64utils.str2uword(input))
if guess==secretnumber { if guess==secretnumber {
return ending(true) return ending(true)

View File

@ -23,23 +23,19 @@
ubyte ypos = 0 ubyte ypos = 0
sub irq() { sub irq() {
Y++ ; delay for alignment Y++ ; slight timing delay to avoid rasterline transition issues
Y++ ; delay for alignment
Y++ ; delay for alignment
Y++ ; delay for alignment
ubyte rasterpos = c64.RASTER ubyte rasterpos = c64.RASTER
if color!=len(colors) { if color!=len(colors) {
c64.EXTCOL = colors[color] c64.EXTCOL = colors[color]
c64.RASTER = rasterpos+barheight
color++ color++
c64.RASTER = rasterpos+barheight
} }
else { else {
Y++ ; delay for alignment
Y++ ; delay for alignment
ypos += 2 ypos += 2
c64.EXTCOL = 0 c64.EXTCOL = 0
c64.RASTER = sin8u(ypos)/2+40
color = 0 color = 0
c64.RASTER = sin8u(ypos)/2+40
} }
} }
} }

View File

@ -214,24 +214,24 @@ waitkey:
sub gameOver() { sub gameOver() {
sound.gameover() sound.gameover()
c64scr.PLOT(7, 7) c64scr.plot(7, 7)
c64.CHROUT('U') c64.CHROUT('U')
c64scr.print("────────────────────────") c64scr.print("────────────────────────")
c64.CHROUT('I') c64.CHROUT('I')
c64scr.PLOT(7, 8) c64scr.plot(7, 8)
c64scr.print("│*** g a m e o v e r ***│") c64scr.print("│*** g a m e o v e r ***│")
c64scr.PLOT(7, 9) c64scr.plot(7, 9)
c64.CHROUT('J') c64.CHROUT('J')
c64scr.print("────────────────────────") c64scr.print("────────────────────────")
c64.CHROUT('K') c64.CHROUT('K')
c64scr.PLOT(7, 18) c64scr.plot(7, 18)
c64.CHROUT('U') c64.CHROUT('U')
c64scr.print("────────────────────────") c64scr.print("────────────────────────")
c64.CHROUT('I') c64.CHROUT('I')
c64scr.PLOT(7, 19) c64scr.plot(7, 19)
c64scr.print("│ f1 for new game │") c64scr.print("│ f1 for new game │")
c64scr.PLOT(7, 20) c64scr.plot(7, 20)
c64.CHROUT('J') c64.CHROUT('J')
c64scr.print("────────────────────────") c64scr.print("────────────────────────")
c64.CHROUT('K') c64.CHROUT('K')
@ -270,34 +270,34 @@ waitkey:
sub drawBoard() { sub drawBoard() {
c64.CLEARSCR() c64.CLEARSCR()
c64.COLOR = 7 c64.COLOR = 7
c64scr.PLOT(1,1) c64scr.plot(1,1)
c64scr.print("irmen's") c64scr.print("irmen's")
c64scr.PLOT(2,2) c64scr.plot(2,2)
c64scr.print("teh▁triz") c64scr.print("teh▁triz")
c64.COLOR = 5 c64.COLOR = 5
c64scr.PLOT(6,4) c64scr.plot(6,4)
c64scr.print("hold:") c64scr.print("hold:")
c64scr.PLOT(2,22) c64scr.plot(2,22)
c64scr.print("speed: ") c64scr.print("speed: ")
c64scr.PLOT(28,3) c64scr.plot(28,3)
c64scr.print("next:") c64scr.print("next:")
c64scr.PLOT(28,10) c64scr.plot(28,10)
c64scr.print("lines:") c64scr.print("lines:")
c64scr.PLOT(28,14) c64scr.plot(28,14)
c64scr.print("score:") c64scr.print("score:")
c64.COLOR = 12 c64.COLOR = 12
c64scr.PLOT(27,18) c64scr.plot(27,18)
c64scr.print("controls:") c64scr.print("controls:")
c64.COLOR = 11 c64.COLOR = 11
c64scr.PLOT(28,19) c64scr.plot(28,19)
c64scr.print(",/ move") c64scr.print(",/ move")
c64scr.PLOT(28,20) c64scr.plot(28,20)
c64scr.print("zx rotate") c64scr.print("zx rotate")
c64scr.PLOT(29,21) c64scr.plot(29,21)
c64scr.print(". descend") c64scr.print(". descend")
c64scr.PLOT(27,22) c64scr.plot(27,22)
c64scr.print("spc drop") c64scr.print("spc drop")
c64scr.PLOT(29,23) c64scr.plot(29,23)
c64scr.print("c hold") c64scr.print("c hold")
c64scr.setcc(boardOffsetX-1, boardOffsetY-2, 255, 0) ; invisible barrier c64scr.setcc(boardOffsetX-1, boardOffsetY-2, 255, 0) ; invisible barrier
@ -331,11 +331,11 @@ waitkey:
sub drawScore() { sub drawScore() {
c64.COLOR=1 c64.COLOR=1
c64scr.PLOT(30,11) c64scr.plot(30,11)
c64scr.print_uw(lines) c64scr.print_uw(lines)
c64scr.PLOT(30,15) c64scr.plot(30,15)
c64scr.print_uw(score) c64scr.print_uw(score)
c64scr.PLOT(9,22) c64scr.plot(9,22)
c64scr.print_ub(speedlevel) c64scr.print_ub(speedlevel)
} }

View File

@ -1,12 +1,15 @@
%import c64utils
%zeropage basicsafe %zeropage basicsafe
~ main { ~ main {
; @todo see problem in looplabelproblem.p8 ubyte @zp var1
; @todo compiler error for using literal values other than 0 or 1 with boolean expressions
sub start() { sub start() {
ubyte @zp var1
A=20
A=var1
Y=main.var1
} }
} }