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 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 ----

View File

@ -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

View File

@ -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

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...")
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...")

View File

@ -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 {

View File

@ -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)
}

View File

@ -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_"

View File

@ -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) {

View File

@ -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>) {

View File

@ -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"

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 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) {

View File

@ -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

View File

@ -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
}
}

View File

@ -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()
}
}

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) },
"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)

View File

@ -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
}
}
}
}

View File

@ -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

View File

@ -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

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
.. 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
------------

View File

@ -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".

View File

@ -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()

View File

@ -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')
}

View File

@ -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")

View File

@ -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 ")

View File

@ -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")

View File

@ -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(':')

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
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")

View File

@ -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)

View File

@ -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
}
}
}

View File

@ -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)
}

View File

@ -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
}
}