mirror of
https://github.com/irmen/prog8.git
synced 2025-06-14 11:23:37 +00:00
Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
c4615591c9 | |||
25e3b599e7 | |||
5502a3e3ee | |||
62ceace941 | |||
7114d3193c | |||
f6bc69139d | |||
f3fc2fe523 | |||
1e045b6a62 | |||
747c9604dd | |||
1e5b2e0be3 | |||
0820716e7b | |||
191707cd37 | |||
223bab21aa | |||
563122ac92 | |||
bc9d00922e | |||
d9d83248fe | |||
f2397527f1 | |||
bf3caaefe1 | |||
1aaf854ef7 | |||
ce40f6f862 | |||
a349599943 | |||
ac7faa8d25 |
@ -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 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 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
|
||||
|
||||
; ---- end of C64 kernal routines ----
|
||||
|
@ -54,40 +54,39 @@ asmsub ubyte2hex (ubyte value @ A) -> clobbers() -> (ubyte @ A, ubyte @ Y) {
|
||||
pha
|
||||
and #$0f
|
||||
tax
|
||||
ldy hex_digits,x
|
||||
ldy _hex_digits,x
|
||||
pla
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
tax
|
||||
lda hex_digits,x
|
||||
lda _hex_digits,x
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
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) -> () {
|
||||
; ---- 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 {{
|
||||
sta c64.SCRATCH_ZPREG
|
||||
tya
|
||||
jsr ubyte2hex
|
||||
sta word2hex_output
|
||||
sty word2hex_output+1
|
||||
sta output
|
||||
sty output+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
jsr ubyte2hex
|
||||
sta word2hex_output+2
|
||||
sty word2hex_output+3
|
||||
sta output+2
|
||||
sty output+3
|
||||
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) -> () {
|
||||
; 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
|
||||
sed ; switch to decimal mode
|
||||
lda #0 ; ensure the result is clear
|
||||
sta word2bcd_bcdbuff+0
|
||||
sta word2bcd_bcdbuff+1
|
||||
sta word2bcd_bcdbuff+2
|
||||
sta bcdbuff+0
|
||||
sta bcdbuff+1
|
||||
sta bcdbuff+2
|
||||
ldy #16 ; the number of source bits
|
||||
|
||||
- asl c64.SCRATCH_ZPB1 ; shift out one bit
|
||||
rol c64.SCRATCH_ZPREG
|
||||
lda word2bcd_bcdbuff+0 ; and add into result
|
||||
adc word2bcd_bcdbuff+0
|
||||
sta word2bcd_bcdbuff+0
|
||||
lda word2bcd_bcdbuff+1 ; propagating any carry
|
||||
adc word2bcd_bcdbuff+1
|
||||
sta word2bcd_bcdbuff+1
|
||||
lda word2bcd_bcdbuff+2 ; ... thru whole result
|
||||
adc word2bcd_bcdbuff+2
|
||||
sta word2bcd_bcdbuff+2
|
||||
lda bcdbuff+0 ; and add into result
|
||||
adc bcdbuff+0
|
||||
sta bcdbuff+0
|
||||
lda bcdbuff+1 ; propagating any carry
|
||||
adc bcdbuff+1
|
||||
sta bcdbuff+1
|
||||
lda bcdbuff+2 ; ... thru whole result
|
||||
adc bcdbuff+2
|
||||
sta bcdbuff+2
|
||||
dey ; and repeat for next bit
|
||||
bne -
|
||||
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)
|
||||
+ rts
|
||||
_had_irqd .byte 0
|
||||
bcdbuff .byte 0,0,0
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
ubyte[5] word2decimal_output = 0
|
||||
asmsub uword2decimal (uword value @ AY) -> clobbers(A,Y) -> () {
|
||||
; ---- convert 16 bit uword in A/Y into decimal string into memory 'word2decimal_output'
|
||||
asmsub uword2decimal (uword value @ AY) -> clobbers(A) -> (ubyte @ Y) {
|
||||
; ---- convert 16 bit uword in A/Y into 0-terminated decimal string into memory 'uword2decimal.output'
|
||||
; returns length of resulting string in Y
|
||||
%asm {{
|
||||
jsr uword2bcd
|
||||
lda word2bcd_bcdbuff+2
|
||||
lda uword2bcd.bcdbuff+2
|
||||
clc
|
||||
adc #'0'
|
||||
sta word2decimal_output
|
||||
sta output
|
||||
ldy #1
|
||||
lda word2bcd_bcdbuff+1
|
||||
lda uword2bcd.bcdbuff+1
|
||||
jsr +
|
||||
lda word2bcd_bcdbuff+0
|
||||
lda uword2bcd.bcdbuff+0
|
||||
|
||||
+ pha
|
||||
lsr a
|
||||
@ -155,153 +155,135 @@ asmsub uword2decimal (uword value @ AY) -> clobbers(A,Y) -> () {
|
||||
lsr a
|
||||
clc
|
||||
adc #'0'
|
||||
sta word2decimal_output,y
|
||||
sta output,y
|
||||
iny
|
||||
pla
|
||||
and #$0f
|
||||
adc #'0'
|
||||
sta word2decimal_output,y
|
||||
sta output,y
|
||||
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
|
||||
dey
|
||||
bpl +
|
||||
sta output,y
|
||||
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
|
||||
;-- return the length of the numeric string at ZPWORD1, in Y
|
||||
output .text "00000", $00 ; 0 terminated
|
||||
|
||||
}}
|
||||
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
- lda (c64.SCRATCH_ZPWORD1),y
|
||||
cmp #'0'
|
||||
bmi +
|
||||
cmp #':' ; one after '9'
|
||||
sty _result
|
||||
sty _result+1
|
||||
_mod lda $ffff,y ; modified
|
||||
sec
|
||||
sbc #48
|
||||
bpl +
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
_done ; return result
|
||||
lda _result
|
||||
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 c64flt_GETADR () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b7f7 ; @todo needed for (slow) str conversion below
|
||||
asmsub c64flt_FTOSWORDYA () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b1aa ; @todo needed for (slow) str conversion below
|
||||
|
||||
asmsub str2uword(str string @ AY) -> clobbers() -> (uword @ AY) {
|
||||
asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) {
|
||||
; -- returns the signed word value of the string number argument in AY
|
||||
; 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)
|
||||
%asm {{
|
||||
;-- convert string (address in A/Y) to uword number in A/Y
|
||||
; @todo don't use the (slow) kernel floating point conversion
|
||||
sta $22
|
||||
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
|
||||
_result = c64.SCRATCH_ZPWORD2
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
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 +
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) {
|
||||
%asm {{
|
||||
;-- convert string (address in A/Y) to signed word number in A/Y
|
||||
; @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
|
||||
sec
|
||||
lda #0
|
||||
sbc _result
|
||||
sta _result
|
||||
lda #0
|
||||
sbc _result+1
|
||||
sta _result+1
|
||||
+ lda _result
|
||||
ldy _result+1
|
||||
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) -> () {
|
||||
%asm {{
|
||||
sei
|
||||
@ -311,7 +293,9 @@ asmsub set_irqvec_excl() -> clobbers(A) -> () {
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
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
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
|
||||
@ -328,10 +312,64 @@ asmsub set_irqvec() -> clobbers(A) -> () {
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
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
|
||||
|
||||
}}
|
||||
_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
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
jmp c64.IRQDFRT
|
||||
@ -403,7 +443,9 @@ asmsub set_rasterirq_excl(uword rasterpos @ AY) -> clobbers(A) -> () {
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
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
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
; 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
|
||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||
; 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
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
; 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
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; 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 {{
|
||||
jsr c64utils.uword2decimal
|
||||
ldy #0
|
||||
- lda c64utils.word2decimal_output,y
|
||||
- lda c64utils.uword2decimal.output,y
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
cpy #5
|
||||
@ -867,25 +909,25 @@ asmsub print_uw (uword value @ AY) -> clobbers(A,Y) -> () {
|
||||
%asm {{
|
||||
jsr c64utils.uword2decimal
|
||||
ldy #0
|
||||
lda c64utils.word2decimal_output
|
||||
lda c64utils.uword2decimal.output
|
||||
cmp #'0'
|
||||
bne _pr_decimal
|
||||
iny
|
||||
lda c64utils.word2decimal_output+1
|
||||
lda c64utils.uword2decimal.output+1
|
||||
cmp #'0'
|
||||
bne _pr_decimal
|
||||
iny
|
||||
lda c64utils.word2decimal_output+2
|
||||
lda c64utils.uword2decimal.output+2
|
||||
cmp #'0'
|
||||
bne _pr_decimal
|
||||
iny
|
||||
lda c64utils.word2decimal_output+3
|
||||
lda c64utils.uword2decimal.output+3
|
||||
cmp #'0'
|
||||
bne _pr_decimal
|
||||
iny
|
||||
|
||||
_pr_decimal
|
||||
lda c64utils.word2decimal_output,y
|
||||
lda c64utils.uword2decimal.output,y
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
cpy #5
|
||||
@ -895,7 +937,7 @@ _pr_decimal
|
||||
}
|
||||
|
||||
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 {{
|
||||
cpy #0
|
||||
bpl +
|
||||
@ -916,7 +958,7 @@ asmsub print_w (word value @ AY) -> clobbers(A,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!
|
||||
|
||||
%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.
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
|
@ -649,7 +649,57 @@ func_read_flags .proc
|
||||
rts
|
||||
.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
|
||||
ldy c64.ESTACK_LO+1,x
|
||||
lda _sinecos8,y
|
||||
@ -1112,8 +1162,7 @@ _gtequ dey
|
||||
_result_minw .word 0
|
||||
.pend
|
||||
|
||||
|
||||
func_len_str .proc
|
||||
func_strlen .proc
|
||||
; -- push length of 0-terminated string on stack
|
||||
jsr peek_address
|
||||
ldy #0
|
||||
@ -1126,15 +1175,6 @@ func_len_str .proc
|
||||
rts
|
||||
.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
|
||||
; -- put a random ubyte on the estack
|
||||
jsr math.randbyte
|
||||
|
@ -1 +1 @@
|
||||
1.4 (beta)
|
||||
1.5 (beta)
|
||||
|
@ -88,7 +88,7 @@ private fun compileMain(args: Array<String>) {
|
||||
println("Syntax check...")
|
||||
val heap = HeapValues()
|
||||
val time1= measureTimeMillis {
|
||||
moduleAst.checkIdentifiers(heap)
|
||||
moduleAst.checkIdentifiers(namespace)
|
||||
}
|
||||
//println(" time1: $time1")
|
||||
val time2 = measureTimeMillis {
|
||||
@ -104,9 +104,7 @@ private fun compileMain(args: Array<String>) {
|
||||
}
|
||||
//println(" time4: $time4")
|
||||
|
||||
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers(heap) // useful for checking symbol usage later?
|
||||
// moduleAst.simplifyExpressions(namespace, heap)
|
||||
// moduleAst.optimizeStatements(namespace, heap)
|
||||
moduleAst.checkIdentifiers(namespace)
|
||||
if(optimize) {
|
||||
// optimize the parse tree
|
||||
println("Optimizing...")
|
||||
|
@ -325,8 +325,11 @@ inline fun <reified T> findParentNode(node: Node): T? {
|
||||
|
||||
interface IStatement : Node {
|
||||
fun process(processor: IAstProcessor) : IStatement
|
||||
fun makeScopedName(name: String): List<String> {
|
||||
// this is usually cached in a lazy property on the statement object itself
|
||||
fun makeScopedName(name: String): String {
|
||||
// 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>()
|
||||
var statementScope = this.parent
|
||||
while(statementScope !is ParentSentinel && statementScope !is Module) {
|
||||
@ -335,8 +338,9 @@ interface IStatement : Node {
|
||||
}
|
||||
statementScope = statementScope.parent
|
||||
}
|
||||
scope.add(name)
|
||||
return scope
|
||||
if(name.isNotEmpty())
|
||||
scope.add(name)
|
||||
return scope.joinToString(".")
|
||||
}
|
||||
}
|
||||
|
||||
@ -507,7 +511,6 @@ class Block(override val name: String,
|
||||
val isInLibrary: Boolean,
|
||||
override val position: Position) : IStatement, INameScope {
|
||||
override lateinit var parent: Node
|
||||
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
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 {
|
||||
override lateinit var parent: Node
|
||||
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -558,6 +560,8 @@ data class Label(val name: String, override val position: Position) : IStatement
|
||||
override fun toString(): String {
|
||||
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)
|
||||
|
||||
val scopedname: String by lazy { makeScopedName(name).joinToString(".") }
|
||||
val scopedname: String by lazy { makeScopedName(name) }
|
||||
|
||||
override fun toString(): String {
|
||||
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
|
||||
|
||||
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++
|
||||
}
|
||||
|
||||
@ -1625,7 +1629,7 @@ class Subroutine(override val name: String,
|
||||
override var statements: MutableList<IStatement>,
|
||||
override val position: Position) : IStatement, INameScope {
|
||||
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) {
|
||||
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 unescape(str: String, position: Position): String {
|
||||
|
@ -139,9 +139,6 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(forLoop.body.isEmpty())
|
||||
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)) {
|
||||
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
|
||||
} else {
|
||||
@ -358,23 +355,17 @@ private class AstChecker(private val namespace: INameScope,
|
||||
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
|
||||
if(assignment.value is FunctionCall) {
|
||||
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.returntypes.size != assignment.targets.size)
|
||||
checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position))
|
||||
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)) {
|
||||
if (thing.second.determineDatatype(namespace, heap, assignment) != thing.first)
|
||||
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))
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
"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
|
||||
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 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 {
|
||||
if(typecast.type in IterableDatatypes)
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -1,50 +1,53 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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> {
|
||||
val checker = AstIdentifiersChecker(heap)
|
||||
fun Module.checkIdentifiers(namespace: INameScope) {
|
||||
val checker = AstIdentifiersChecker(namespace)
|
||||
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) {
|
||||
val scope = variable.first.definingScope()
|
||||
scope.statements.add(variable.second)
|
||||
val parent = variable.first.parent
|
||||
when {
|
||||
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)
|
||||
parent.value = idref
|
||||
}
|
||||
parent is IFunctionCall -> {
|
||||
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)
|
||||
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)")
|
||||
}
|
||||
variable.second.linkParents(scope as Node)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
var symbols: MutableMap<String, IStatement> = mutableMapOf()
|
||||
var blocks: MutableMap<String, Block> = mutableMapOf()
|
||||
private set
|
||||
|
||||
fun result(): List<AstException> {
|
||||
@ -52,17 +55,16 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
}
|
||||
|
||||
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 {
|
||||
val scopedName = block.scopedname
|
||||
val existing = symbols[scopedName]
|
||||
if(existing!=null) {
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null)
|
||||
nameError(block.name, block.position, existing)
|
||||
} else {
|
||||
symbols[scopedName] = block
|
||||
}
|
||||
else
|
||||
blocks[block.name] = block
|
||||
|
||||
return super.process(block)
|
||||
}
|
||||
|
||||
@ -85,13 +87,10 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
// the builtin functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
|
||||
|
||||
val scopedName = decl.scopedname
|
||||
val existing = symbols[scopedName]
|
||||
if(existing!=null) {
|
||||
val existing = namespace.lookup(listOf(decl.name), decl)
|
||||
if (existing != null && existing !== decl)
|
||||
nameError(decl.name, decl.position, existing)
|
||||
} else {
|
||||
symbols[scopedName] = decl
|
||||
}
|
||||
|
||||
return super.process(decl)
|
||||
}
|
||||
|
||||
@ -103,13 +102,9 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
||||
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
||||
|
||||
val scopedName = subroutine.scopedname
|
||||
val existing = symbols[scopedName]
|
||||
if (existing != null) {
|
||||
val existing = namespace.lookup(listOf(subroutine.name), subroutine)
|
||||
if (existing != null && existing !== subroutine)
|
||||
nameError(subroutine.name, subroutine.position, existing)
|
||||
} else {
|
||||
symbols[scopedName] = subroutine
|
||||
}
|
||||
|
||||
// check that there are no local variables that redefine the subroutine's parameters
|
||||
val allDefinedNames = subroutine.allLabelsAndVariables()
|
||||
@ -146,13 +141,9 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
// the builtin functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", label.position))
|
||||
} else {
|
||||
val scopedName = label.scopedname
|
||||
val existing = symbols[scopedName]
|
||||
if (existing != null) {
|
||||
val existing = namespace.lookup(listOf(label.name), label)
|
||||
if (existing != null && existing !== label)
|
||||
nameError(label.name, label.position, existing)
|
||||
} else {
|
||||
symbols[scopedName] = label
|
||||
}
|
||||
}
|
||||
return super.process(label)
|
||||
}
|
||||
@ -228,13 +219,16 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
|
||||
internal val anonymousVariablesFromHeap = mutableSetOf<Pair<LiteralValue, VarDecl>>()
|
||||
|
||||
|
||||
override fun process(literalValue: LiteralValue): LiteralValue {
|
||||
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.
|
||||
// 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))
|
||||
}
|
||||
return super.process(literalValue)
|
||||
}
|
||||
}
|
||||
|
||||
private const val autoHeapValuePrefix = "auto_heap_value_"
|
||||
|
@ -233,7 +233,7 @@ private class VarInitValueCreator: IAstProcessor {
|
||||
val scope = decl.definingScope()
|
||||
if(scope !in vardeclsToAdd)
|
||||
vardeclsToAdd[scope] = mutableListOf()
|
||||
vardeclsToAdd[scope]!!.add(decl.asDefaultValueDecl(null))
|
||||
vardeclsToAdd.getValue(scope).add(decl.asDefaultValueDecl(null))
|
||||
val declvalue = decl.value!!
|
||||
val value =
|
||||
if(declvalue is LiteralValue) {
|
||||
|
@ -157,9 +157,8 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
|
||||
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)
|
||||
prog.label("block."+block.scopedname, false)
|
||||
prog.line(block.position)
|
||||
translate(block.statements)
|
||||
return super.process(block)
|
||||
@ -377,7 +376,16 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -452,8 +460,8 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
} else {
|
||||
// regular if..else branching
|
||||
val labelElse = makeLabel("else")
|
||||
val labelEnd = makeLabel("end")
|
||||
val labelElse = makeLabel(branch, "else")
|
||||
val labelEnd = makeLabel(branch, "end")
|
||||
val opcode = branchOpcode(branch, true)
|
||||
if (branch.elsepart.isEmpty()) {
|
||||
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++
|
||||
return "_prog8stmt_${generatedLabelSequenceNumber}_$postfix"
|
||||
return "${scopeStmt.makeScopedName("")}.<s-$generatedLabelSequenceNumber-$postfix>"
|
||||
}
|
||||
|
||||
private fun translate(stmt: IfStatement) {
|
||||
@ -519,13 +527,13 @@ internal class Compiler(private val rootModule: Module,
|
||||
in WordDatatypes -> Opcode.JZW
|
||||
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
|
||||
}
|
||||
val labelEnd = makeLabel("end")
|
||||
val labelEnd = makeLabel(stmt, "end")
|
||||
if(stmt.elsepart.isEmpty()) {
|
||||
prog.instr(conditionJumpOpcode, callLabel = labelEnd)
|
||||
translate(stmt.truepart)
|
||||
prog.label(labelEnd)
|
||||
} else {
|
||||
val labelElse = makeLabel("else")
|
||||
val labelElse = makeLabel(stmt, "else")
|
||||
prog.instr(conditionJumpOpcode, callLabel = labelElse)
|
||||
translate(stmt.truepart)
|
||||
prog.instr(Opcode.JUMP, callLabel = labelEnd)
|
||||
@ -635,10 +643,8 @@ internal class Compiler(private val rootModule: Module,
|
||||
val funcname = expr.target.nameInSource[0]
|
||||
translateBuiltinFunctionCall(funcname, expr.arglist)
|
||||
} else {
|
||||
when(target) {
|
||||
is Subroutine -> translateSubroutineCall(target, expr.arglist, expr.position)
|
||||
else -> TODO("non-builtin-function call to $target")
|
||||
}
|
||||
if (target is Subroutine) translateSubroutineCall(target, expr.arglist, expr.position)
|
||||
else TODO("non-builtin-function call to $target")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> translate(expr)
|
||||
@ -661,7 +667,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
in ArrayDatatypes -> {
|
||||
if(lv.heapId==null)
|
||||
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")
|
||||
}
|
||||
@ -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
|
||||
// (in reversed order) otherwise the asm-subroutine can't be used in expressions.
|
||||
for(rv in subroutine.asmReturnvaluesRegisters.reversed()) {
|
||||
if(rv.statusflag!=null)
|
||||
TODO("not yet supported: return values in cpu status flag $rv $subroutine")
|
||||
when(rv.registerOrPair) {
|
||||
A,X,Y -> prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = rv.registerOrPair.name)
|
||||
AX, AY, XY -> prog.instr(Opcode.PUSH_VAR_WORD, callLabel = rv.registerOrPair.name)
|
||||
null -> {}
|
||||
if(rv.statusflag!=null) {
|
||||
if (rv.statusflag == Statusflag.Pc) {
|
||||
prog.instr(Opcode.CARRY_TO_A)
|
||||
prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = Register.A.name)
|
||||
}
|
||||
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) {
|
||||
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(namespace)
|
||||
if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) {
|
||||
// TODO check correctness of multi-return values (they should be on the stack rather than directly assigned!)
|
||||
// we're dealing with the one case where multiple assignment targets are allowed: a call to an asmsub with multiple return values
|
||||
// for now, we only support multiple return values as long as they're returned in registers as well.
|
||||
if(targetStmt.asmReturnvaluesRegisters.isEmpty())
|
||||
throw CompilerException("we only support multiple return values / assignment when the asmsub returns values in registers")
|
||||
// if the result registers are not assigned in the exact same registers, or in variables, we need some code
|
||||
if(stmt.targets.all{it.register!=null}) {
|
||||
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)
|
||||
// this is the only 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)
|
||||
if(stmt.targets.size!=targetStmt.asmReturnvaluesRegisters.size)
|
||||
throw CompilerException("asmsub number of return values doesn't match number of assignment targets ${stmt.position}")
|
||||
for(target in stmt.targets) {
|
||||
val dt = target.determineDatatype(namespace, heap, stmt)
|
||||
popValueIntoTarget(target, dt!!)
|
||||
}
|
||||
} 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) {
|
||||
when {
|
||||
assignTarget.identifier != null -> {
|
||||
@ -1806,9 +1763,9 @@ internal class Compiler(private val rootModule: Module,
|
||||
* break:
|
||||
* nop
|
||||
*/
|
||||
val loopLabel = makeLabel("loop")
|
||||
val continueLabel = makeLabel("continue")
|
||||
val breakLabel = makeLabel("break")
|
||||
val loopLabel = makeLabel(loop, "loop")
|
||||
val continueLabel = makeLabel(loop, "continue")
|
||||
val breakLabel = makeLabel(loop, "break")
|
||||
val indexVarType = if (numElements <= 255) DataType.UBYTE else DataType.UWORD
|
||||
val indexVar = loop.body.getLabelOrVariable(ForLoop.iteratorLoopcounterVarname) as VarDecl
|
||||
|
||||
@ -1867,9 +1824,9 @@ internal class Compiler(private val rootModule: Module,
|
||||
* break:
|
||||
* nop
|
||||
*/
|
||||
val loopLabel = makeLabel("loop")
|
||||
val continueLabel = makeLabel("continue")
|
||||
val breakLabel = makeLabel("break")
|
||||
val loopLabel = makeLabel(body, "loop")
|
||||
val continueLabel = makeLabel(body, "continue")
|
||||
val breakLabel = makeLabel(body, "break")
|
||||
|
||||
continueStmtLabelStack.push(continueLabel)
|
||||
breakStmtLabelStack.push(breakLabel)
|
||||
@ -1972,9 +1929,9 @@ internal class Compiler(private val rootModule: Module,
|
||||
startAssignment.linkParents(body)
|
||||
translate(startAssignment)
|
||||
|
||||
val loopLabel = makeLabel("loop")
|
||||
val continueLabel = makeLabel("continue")
|
||||
val breakLabel = makeLabel("break")
|
||||
val loopLabel = makeLabel(body, "loop")
|
||||
val continueLabel = makeLabel(body, "continue")
|
||||
val breakLabel = makeLabel(body, "break")
|
||||
val literalStepValue = (range.step as? LiteralValue)?.asNumericValue?.toInt()
|
||||
|
||||
continueStmtLabelStack.push(continueLabel)
|
||||
@ -2076,9 +2033,9 @@ internal class Compiler(private val rootModule: Module,
|
||||
* break:
|
||||
* nop
|
||||
*/
|
||||
val loopLabel = makeLabel("loop")
|
||||
val breakLabel = makeLabel("break")
|
||||
val continueLabel = makeLabel("continue")
|
||||
val loopLabel = makeLabel(stmt, "loop")
|
||||
val breakLabel = makeLabel(stmt, "break")
|
||||
val continueLabel = makeLabel(stmt, "continue")
|
||||
prog.line(stmt.position)
|
||||
breakStmtLabelStack.push(breakLabel)
|
||||
continueStmtLabelStack.push(continueLabel)
|
||||
@ -2114,9 +2071,9 @@ internal class Compiler(private val rootModule: Module,
|
||||
* break:
|
||||
* nop
|
||||
*/
|
||||
val loopLabel = makeLabel("loop")
|
||||
val continueLabel = makeLabel("continue")
|
||||
val breakLabel = makeLabel("break")
|
||||
val loopLabel = makeLabel(stmt, "loop")
|
||||
val continueLabel = makeLabel(stmt, "continue")
|
||||
val breakLabel = makeLabel(stmt, "break")
|
||||
prog.line(stmt.position)
|
||||
breakStmtLabelStack.push(breakLabel)
|
||||
continueStmtLabelStack.push(continueLabel)
|
||||
@ -2137,13 +2094,6 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
|
||||
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)
|
||||
val sourceDt = expr.expression.resultingDatatype(namespace, heap) ?: throw CompilerException("don't know what type to cast")
|
||||
if(sourceDt==expr.type)
|
||||
@ -2233,7 +2183,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
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>) {
|
||||
|
@ -8,15 +8,21 @@ open class Instruction(val opcode: Opcode,
|
||||
val callLabel: String? = null,
|
||||
val callLabel2: String? = null)
|
||||
{
|
||||
lateinit var next: Instruction
|
||||
var nextAlt: Instruction? = null
|
||||
var branchAddress: Int? = null
|
||||
|
||||
override fun toString(): String {
|
||||
val argStr = arg?.toString() ?: ""
|
||||
val result =
|
||||
when {
|
||||
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 -> {
|
||||
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
|
||||
"syscall $syscall"
|
||||
|
@ -11,8 +11,7 @@ import java.nio.file.Path
|
||||
|
||||
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val importedFrom: Path) {
|
||||
|
||||
class ProgramBlock(val scopedname: String,
|
||||
val shortname: String,
|
||||
class ProgramBlock(val name: String,
|
||||
var address: Int?,
|
||||
val instructions: MutableList<Instruction> = mutableListOf(),
|
||||
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)
|
||||
}
|
||||
|
||||
fun newBlock(scopedname: String, shortname: String, address: Int?, options: Set<String>) {
|
||||
currentBlock = ProgramBlock(scopedname, shortname, address, force_output="force_output" in options)
|
||||
fun newBlock(name: String, address: Int?, options: Set<String>) {
|
||||
currentBlock = ProgramBlock(name, address, force_output="force_output" in options)
|
||||
blocks.add(currentBlock)
|
||||
}
|
||||
|
||||
@ -472,7 +471,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
}
|
||||
out.println("%end_heap")
|
||||
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")
|
||||
for(variable in blk.variables) {
|
||||
|
@ -244,7 +244,6 @@ enum class Opcode {
|
||||
JZW, // branch if value is zero (word)
|
||||
JNZW, // branch if value is not zero (word)
|
||||
|
||||
|
||||
// subroutines
|
||||
CALL,
|
||||
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
|
||||
SEI, // set 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
|
||||
RSAVEX, // save just X (the evaluation stack pointer)
|
||||
RRESTORE, // restore all internal registers and status flags
|
||||
|
@ -23,10 +23,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
private var breakpointCounter = 0
|
||||
|
||||
init {
|
||||
// Because 64tass understands scoped names via .proc / .block,
|
||||
// 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!
|
||||
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
|
||||
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
|
||||
for(block in program.blocks) {
|
||||
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)
|
||||
}
|
||||
}.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(
|
||||
block.scopedname,
|
||||
block.shortname,
|
||||
block.name,
|
||||
block.address,
|
||||
newinstructions,
|
||||
newvars,
|
||||
newConstants,
|
||||
newMempointers,
|
||||
newlabels,
|
||||
force_output = block.force_output)
|
||||
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 {
|
||||
if(' ' in scoped)
|
||||
return scoped
|
||||
val blockLocal: Boolean
|
||||
var name = when {
|
||||
block==null -> {
|
||||
blockLocal=true
|
||||
scoped
|
||||
}
|
||||
scoped.startsWith("${block.shortname}.") -> {
|
||||
blockLocal = true
|
||||
scoped.substring(block.shortname.length+1)
|
||||
}
|
||||
scoped.startsWith("block.") -> {
|
||||
blockLocal = false
|
||||
scoped
|
||||
}
|
||||
else -> {
|
||||
blockLocal = false
|
||||
scoped
|
||||
}
|
||||
var name = if (block!=null && scoped.startsWith("${block.name}.")) {
|
||||
blockLocal = true
|
||||
scoped.substring(block.name.length+1)
|
||||
}
|
||||
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=="-")
|
||||
return "-"
|
||||
if(blockLocal)
|
||||
@ -199,7 +186,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
for(block in program.blocks) {
|
||||
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name==initvarsSubName } as? LabelInstr
|
||||
if(initVarsLabel!=null)
|
||||
out(" jsr ${block.scopedname}.${initVarsLabel.name}")
|
||||
out(" jsr ${block.name}.${initVarsLabel.name}")
|
||||
}
|
||||
out(" clc")
|
||||
when(zeropage.exitProgramStrategy) {
|
||||
@ -222,9 +209,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
|
||||
private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
|
||||
block = blk
|
||||
out("\n; ---- block: '${block.shortname}' ----")
|
||||
out("\n; ---- block: '${block.name}' ----")
|
||||
if(!blk.force_output)
|
||||
out("${block.shortname}\t.proc\n")
|
||||
out("${block.name}\t.proc\n")
|
||||
if(block.address!=null) {
|
||||
out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'")
|
||||
out("* = ${block.address?.toHex()}")
|
||||
@ -232,14 +219,14 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
|
||||
// deal with zeropage 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]
|
||||
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)
|
||||
if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) {
|
||||
try {
|
||||
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
|
||||
blk.variablesMarkedForZeropage.add(variable.key)
|
||||
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 -> {
|
||||
val rawStr = heap.get(v.second.heapId).str!!
|
||||
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))
|
||||
out(" .byte " + chunk.joinToString())
|
||||
}
|
||||
@ -427,12 +414,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
private fun simpleInstr2Asm(ins: Instruction): String? {
|
||||
// a label 'instruction' is simply translated into a asm label
|
||||
if(ins is LabelInstr) {
|
||||
if(ins.name.startsWith("block."))
|
||||
return ""
|
||||
|
||||
val labelresult =
|
||||
if(ins.name.startsWith("${block.shortname}."))
|
||||
ins.name.substring(block.shortname.length+1)
|
||||
if(ins.name.startsWith("${block.name}."))
|
||||
ins.name.substring(block.name.length+1)
|
||||
else
|
||||
ins.name
|
||||
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.SEI -> " sei"
|
||||
Opcode.CLI -> " cli"
|
||||
Opcode.CARRY_TO_A -> " lda #0 | adc #0"
|
||||
Opcode.JUMP -> {
|
||||
if(ins.callLabel!=null)
|
||||
" jmp ${ins.callLabel}"
|
||||
@ -476,7 +461,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
Opcode.DISCARD_BYTE -> " inx"
|
||||
Opcode.DISCARD_WORD -> " 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 -> {
|
||||
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()}")
|
||||
@ -1255,9 +1240,6 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
Opcode.ROR2_WORD -> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -1055,11 +1055,12 @@ class Petscii {
|
||||
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
|
||||
return text.map {
|
||||
val petscii = lookup[it]
|
||||
if(petscii==null) {
|
||||
val case = if(lowercase) "lower" else "upper"
|
||||
petscii?.toShort() ?: if(it=='\u0000')
|
||||
0.toShort()
|
||||
else {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}case Petscii character for '$it'")
|
||||
}
|
||||
petscii.toShort()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1072,11 +1073,12 @@ class Petscii {
|
||||
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
|
||||
return text.map{
|
||||
val screencode = lookup[it]
|
||||
if(screencode==null) {
|
||||
val case = if(lowercase) "lower" else "upper"
|
||||
screencode?.toShort() ?: if(it=='\u0000')
|
||||
0.toShort()
|
||||
else {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '$it'")
|
||||
}
|
||||
screencode.toShort()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) },
|
||||
"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) },
|
||||
// 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) },
|
||||
"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) },
|
||||
@ -84,6 +84,7 @@ val BuiltinFunctions = mapOf(
|
||||
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
|
||||
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
|
||||
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_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), 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)
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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)
|
||||
|
@ -11,7 +11,7 @@ class Program (val name: String,
|
||||
val program: MutableList<Instruction>,
|
||||
val variables: Map<String, Value>,
|
||||
val memoryPointers: Map<String, Pair<Int, DataType>>,
|
||||
val labels: Map<String, Instruction>,
|
||||
val labels: Map<String, Int>,
|
||||
val memory: Map<Int, List<Value>>,
|
||||
val heap: HeapValues)
|
||||
{
|
||||
@ -20,7 +20,6 @@ class Program (val name: String,
|
||||
program.add(LabelInstr("____program_end", false))
|
||||
program.add(Instruction(Opcode.TERMINATE))
|
||||
program.add(Instruction(Opcode.NOP))
|
||||
connect()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -31,7 +30,7 @@ class Program (val name: String,
|
||||
val program = mutableListOf<Instruction>()
|
||||
val variables = mutableMapOf<String, Value>()
|
||||
val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>()
|
||||
val labels = mutableMapOf<String, Instruction>()
|
||||
val labels = mutableMapOf<String, Int>()
|
||||
|
||||
while(lines.hasNext()) {
|
||||
val (lineNr, line) = lines.next()
|
||||
@ -53,7 +52,7 @@ class Program (val name: String,
|
||||
program: MutableList<Instruction>,
|
||||
variables: MutableMap<String, Value>,
|
||||
memoryPointers: MutableMap<String, Pair<Int, DataType>>,
|
||||
labels: MutableMap<String, Instruction>)
|
||||
labels: MutableMap<String, Int>)
|
||||
{
|
||||
while(true) {
|
||||
val (_, line) = lines.next()
|
||||
@ -67,8 +66,10 @@ class Program (val name: String,
|
||||
loadMemoryPointers(lines, memoryPointers, heap)
|
||||
else if(line=="%instructions") {
|
||||
val (blockInstructions, blockLabels) = loadInstructions(lines, heap)
|
||||
val baseIndex = program.size
|
||||
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)
|
||||
}
|
||||
Opcode.SYSCALL -> {
|
||||
val call = Syscall.valueOf(args!!)
|
||||
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
|
||||
if(args!! in syscallNames) {
|
||||
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 -> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,8 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
|
||||
private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB)
|
||||
private val g2d = image.graphics as Graphics2D
|
||||
private var cursorX: Int=0
|
||||
private var cursorY: Int=0
|
||||
|
||||
init {
|
||||
val size = Dimension(image.width * SCALING, image.height * SCALING)
|
||||
@ -48,6 +50,8 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
fun clearScreen(color: Int) {
|
||||
g2d.background = palette[color and 15]
|
||||
g2d.clearRect(0, 0, BitmapScreenPanel.SCREENWIDTH, BitmapScreenPanel.SCREENHEIGHT)
|
||||
cursorX = 0
|
||||
cursorY = 0
|
||||
}
|
||||
fun setPixel(x: Int, y: Int, color: Int) {
|
||||
image.setRGB(x, y, palette[color and 15].rgb)
|
||||
@ -56,25 +60,74 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
g2d.color = palette[color and 15]
|
||||
g2d.drawLine(x1, y1, x2, y2)
|
||||
}
|
||||
fun writeText(x: Int, y: Int, text: String, color: Int) {
|
||||
if(color!=1) {
|
||||
TODO("text can only be white for now")
|
||||
}
|
||||
var xx=x
|
||||
var yy=y
|
||||
for(sc in Petscii.encodeScreencode(text, true)) {
|
||||
setChar(xx, yy, sc)
|
||||
xx++
|
||||
if(xx>=(SCREENWIDTH/8)) {
|
||||
yy++
|
||||
xx=0
|
||||
fun printText(text: String, color: Int, lowercase: Boolean) {
|
||||
val lines = text.split('\n')
|
||||
for(line in lines.withIndex()) {
|
||||
printTextSingleLine(line.value, color, lowercase)
|
||||
if(line.index<lines.size-1) {
|
||||
cursorX=0
|
||||
cursorY++
|
||||
}
|
||||
}
|
||||
}
|
||||
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) {
|
||||
g2d.clearRect(8*x, 8*y, 8, 8)
|
||||
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 {
|
||||
const val SCREENWIDTH = 320
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -58,7 +58,7 @@ class TestStackVmOpcodes {
|
||||
private fun makeProg(ins: MutableList<Instruction>,
|
||||
vars: Map<String, Value>?=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 {
|
||||
val heap = HeapValues()
|
||||
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.TERMINATE),
|
||||
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.step(2)
|
||||
assertEquals("", vm.sourceLine)
|
||||
@ -771,7 +771,7 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
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)
|
||||
assertFalse(vm.P_carry)
|
||||
vm.step(2)
|
||||
@ -792,7 +792,7 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
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.step(2)
|
||||
assertEquals("", vm.sourceLine)
|
||||
@ -812,7 +812,7 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
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.step(2)
|
||||
assertEquals("", vm.sourceLine)
|
||||
@ -832,7 +832,7 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
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.step(2)
|
||||
assertEquals("", vm.sourceLine)
|
||||
@ -852,7 +852,7 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
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.step(2)
|
||||
assertEquals("", vm.sourceLine)
|
||||
@ -869,7 +869,7 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.LINE, callLabel = "string1"),
|
||||
Instruction(Opcode.TERMINATE),
|
||||
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.step(2)
|
||||
assertEquals("string2", vm.sourceLine)
|
||||
@ -889,7 +889,7 @@ class TestStackVmOpcodes {
|
||||
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)
|
||||
vm.step(2)
|
||||
assertEquals("string1", vm.sourceLine)
|
||||
@ -906,12 +906,12 @@ class TestStackVmOpcodes {
|
||||
Instruction(Opcode.LINE, callLabel = "called"),
|
||||
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.step(1)
|
||||
assertEquals("", vm.sourceLine)
|
||||
assertEquals(1, vm.callstack.size)
|
||||
assertSame(ins[1], vm.callstack.peek())
|
||||
assertSame(1, vm.callstack.peek())
|
||||
vm.step(1)
|
||||
assertEquals("called", vm.sourceLine)
|
||||
vm.step(1)
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
@ -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
|
||||
|
||||
|
||||
.. 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
|
||||
------------
|
||||
|
||||
|
@ -604,8 +604,11 @@ ln(x)
|
||||
log2(x)
|
||||
Base 2 logarithm.
|
||||
|
||||
sqrt16(w)
|
||||
16 bit unsigned integer Square root. Result is unsigned byte.
|
||||
|
||||
sqrt(x)
|
||||
Square root.
|
||||
Floating point Square root.
|
||||
|
||||
round(x)
|
||||
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: 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!
|
||||
(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)
|
||||
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
|
||||
|
@ -362,14 +362,13 @@ Operators
|
||||
|
||||
.. todo::
|
||||
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?
|
||||
|
||||
This can replace the ``memory`` var decl prefix as well, instead of
|
||||
``memory uword var = $c000`` we could write ``&uword var = $c000``
|
||||
|
||||
|
||||
|
||||
arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%``
|
||||
``+``, ``-``, ``*``, ``/`` 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)
|
||||
@ -436,10 +435,20 @@ You call a subroutine like this::
|
||||
[ result = ] subroutinename_or_address ( [argument...] )
|
||||
|
||||
; 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
|
||||
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()
|
||||
|
||||
|
||||
|
||||
|
@ -44,10 +44,10 @@ sub start() {
|
||||
|
||||
sub print_notes(ubyte n1, ubyte n2) {
|
||||
c64.CHROUT('\n')
|
||||
c64scr.PLOT(n1/2, 24)
|
||||
c64scr.plot(n1/2, 24)
|
||||
c64.COLOR=7
|
||||
c64.CHROUT('Q')
|
||||
c64scr.PLOT(n2/2, 24)
|
||||
c64scr.plot(n2/2, 24)
|
||||
c64.COLOR=4
|
||||
c64.CHROUT('Q')
|
||||
}
|
||||
|
@ -24,7 +24,7 @@
|
||||
c64scr.clear_screenchars(32)
|
||||
draw_edges()
|
||||
time+=0.2
|
||||
c64scr.PLOT(0,0)
|
||||
c64scr.plot(0,0)
|
||||
c64scr.print("3d cube! (float) ")
|
||||
c64scr.print_ub(c64.TIME_LO)
|
||||
c64scr.print(" jiffies/frame")
|
||||
|
@ -89,7 +89,7 @@
|
||||
anglex-=500
|
||||
angley+=217
|
||||
anglez+=452
|
||||
c64scr.PLOT(0,0)
|
||||
c64scr.plot(0,0)
|
||||
c64scr.print("3d cube! (sprites) ")
|
||||
c64scr.print_ub(c64.TIME_LO)
|
||||
c64scr.print(" jiffies/frame ")
|
||||
|
@ -29,7 +29,7 @@
|
||||
anglex+=1000
|
||||
angley+=433
|
||||
anglez+=907
|
||||
c64scr.PLOT(0,0)
|
||||
c64scr.plot(0,0)
|
||||
c64scr.print("3d cube! (integer) ")
|
||||
c64scr.print_ub(c64.TIME_LO)
|
||||
c64scr.print(" jiffies/frame")
|
||||
|
@ -32,7 +32,6 @@
|
||||
float minutes = floor(clock_seconds / 60)
|
||||
clock_seconds = floor(clock_seconds - minutes * 60.0)
|
||||
|
||||
; @todo implement strcpy/strcat/strlen?
|
||||
c64scr.print("system time in ti$ is ")
|
||||
c64flt.print_f(hours)
|
||||
c64.CHROUT(':')
|
||||
|
@ -1,18 +0,0 @@
|
||||
|
||||
~ main {
|
||||
|
||||
sub start() {
|
||||
|
||||
if A>10 {
|
||||
A=44
|
||||
while true {
|
||||
;derp
|
||||
}
|
||||
} else {
|
||||
|
||||
gameover:
|
||||
goto gameover
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
const ubyte max_iter = 16
|
||||
|
||||
sub start() {
|
||||
c64scr.print("calculating mandelbrot fractal...\n")
|
||||
c64scr.print("calculating mandelbrot fractal...")
|
||||
|
||||
c64.TIME_HI=0
|
||||
c64.TIME_MID=0
|
||||
@ -42,7 +42,7 @@
|
||||
float duration = floor(((c64.TIME_LO as float)
|
||||
+ 256.0*(c64.TIME_MID as float)
|
||||
+ 65536.0*(c64.TIME_HI as float))/60.0)
|
||||
c64scr.PLOT(0, 21)
|
||||
c64scr.plot(0, 21)
|
||||
c64scr.print("finished in ")
|
||||
c64flt.print_f(duration)
|
||||
c64scr.print(" seconds!\n")
|
||||
|
@ -29,7 +29,7 @@
|
||||
c64scr.print("es")
|
||||
c64scr.print(" left.\nWhat is your next guess? ")
|
||||
c64scr.input_chars(input)
|
||||
ubyte guess = c64utils.str2ubyte(input)
|
||||
ubyte guess = lsb(c64utils.str2uword(input))
|
||||
|
||||
if guess==secretnumber {
|
||||
return ending(true)
|
||||
|
@ -23,23 +23,19 @@
|
||||
ubyte ypos = 0
|
||||
|
||||
sub irq() {
|
||||
Y++ ; delay for alignment
|
||||
Y++ ; delay for alignment
|
||||
Y++ ; delay for alignment
|
||||
Y++ ; delay for alignment
|
||||
Y++ ; slight timing delay to avoid rasterline transition issues
|
||||
ubyte rasterpos = c64.RASTER
|
||||
|
||||
if color!=len(colors) {
|
||||
c64.EXTCOL = colors[color]
|
||||
c64.RASTER = rasterpos+barheight
|
||||
color++
|
||||
c64.RASTER = rasterpos+barheight
|
||||
}
|
||||
else {
|
||||
Y++ ; delay for alignment
|
||||
Y++ ; delay for alignment
|
||||
ypos += 2
|
||||
c64.EXTCOL = 0
|
||||
c64.RASTER = sin8u(ypos)/2+40
|
||||
color = 0
|
||||
c64.RASTER = sin8u(ypos)/2+40
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -214,24 +214,24 @@ waitkey:
|
||||
|
||||
sub gameOver() {
|
||||
sound.gameover()
|
||||
c64scr.PLOT(7, 7)
|
||||
c64scr.plot(7, 7)
|
||||
c64.CHROUT('U')
|
||||
c64scr.print("────────────────────────")
|
||||
c64.CHROUT('I')
|
||||
c64scr.PLOT(7, 8)
|
||||
c64scr.plot(7, 8)
|
||||
c64scr.print("│*** g a m e o v e r ***│")
|
||||
c64scr.PLOT(7, 9)
|
||||
c64scr.plot(7, 9)
|
||||
c64.CHROUT('J')
|
||||
c64scr.print("────────────────────────")
|
||||
c64.CHROUT('K')
|
||||
|
||||
c64scr.PLOT(7, 18)
|
||||
c64scr.plot(7, 18)
|
||||
c64.CHROUT('U')
|
||||
c64scr.print("────────────────────────")
|
||||
c64.CHROUT('I')
|
||||
c64scr.PLOT(7, 19)
|
||||
c64scr.plot(7, 19)
|
||||
c64scr.print("│ f1 for new game │")
|
||||
c64scr.PLOT(7, 20)
|
||||
c64scr.plot(7, 20)
|
||||
c64.CHROUT('J')
|
||||
c64scr.print("────────────────────────")
|
||||
c64.CHROUT('K')
|
||||
@ -270,34 +270,34 @@ waitkey:
|
||||
sub drawBoard() {
|
||||
c64.CLEARSCR()
|
||||
c64.COLOR = 7
|
||||
c64scr.PLOT(1,1)
|
||||
c64scr.plot(1,1)
|
||||
c64scr.print("irmen's")
|
||||
c64scr.PLOT(2,2)
|
||||
c64scr.plot(2,2)
|
||||
c64scr.print("teh▁triz")
|
||||
c64.COLOR = 5
|
||||
c64scr.PLOT(6,4)
|
||||
c64scr.plot(6,4)
|
||||
c64scr.print("hold:")
|
||||
c64scr.PLOT(2,22)
|
||||
c64scr.plot(2,22)
|
||||
c64scr.print("speed: ")
|
||||
c64scr.PLOT(28,3)
|
||||
c64scr.plot(28,3)
|
||||
c64scr.print("next:")
|
||||
c64scr.PLOT(28,10)
|
||||
c64scr.plot(28,10)
|
||||
c64scr.print("lines:")
|
||||
c64scr.PLOT(28,14)
|
||||
c64scr.plot(28,14)
|
||||
c64scr.print("score:")
|
||||
c64.COLOR = 12
|
||||
c64scr.PLOT(27,18)
|
||||
c64scr.plot(27,18)
|
||||
c64scr.print("controls:")
|
||||
c64.COLOR = 11
|
||||
c64scr.PLOT(28,19)
|
||||
c64scr.plot(28,19)
|
||||
c64scr.print(",/ move")
|
||||
c64scr.PLOT(28,20)
|
||||
c64scr.plot(28,20)
|
||||
c64scr.print("zx rotate")
|
||||
c64scr.PLOT(29,21)
|
||||
c64scr.plot(29,21)
|
||||
c64scr.print(". descend")
|
||||
c64scr.PLOT(27,22)
|
||||
c64scr.plot(27,22)
|
||||
c64scr.print("spc drop")
|
||||
c64scr.PLOT(29,23)
|
||||
c64scr.plot(29,23)
|
||||
c64scr.print("c hold")
|
||||
|
||||
c64scr.setcc(boardOffsetX-1, boardOffsetY-2, 255, 0) ; invisible barrier
|
||||
@ -331,11 +331,11 @@ waitkey:
|
||||
|
||||
sub drawScore() {
|
||||
c64.COLOR=1
|
||||
c64scr.PLOT(30,11)
|
||||
c64scr.plot(30,11)
|
||||
c64scr.print_uw(lines)
|
||||
c64scr.PLOT(30,15)
|
||||
c64scr.plot(30,15)
|
||||
c64scr.print_uw(score)
|
||||
c64scr.PLOT(9,22)
|
||||
c64scr.plot(9,22)
|
||||
c64scr.print_ub(speedlevel)
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
~ main {
|
||||
|
||||
; @todo see problem in looplabelproblem.p8
|
||||
; @todo compiler error for using literal values other than 0 or 1 with boolean expressions
|
||||
ubyte @zp var1
|
||||
|
||||
sub start() {
|
||||
|
||||
ubyte @zp var1
|
||||
A=20
|
||||
A=var1
|
||||
Y=main.var1
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user