mirror of
https://github.com/irmen/prog8.git
synced 2025-06-18 23:23:40 +00:00
Compare commits
103 Commits
Author | SHA1 | Date | |
---|---|---|---|
2ba6c9ccbe | |||
3eaf111e7d | |||
30da26b9a9 | |||
e35ad0cc8f | |||
1a36302cf1 | |||
82a28bb555 | |||
c1ce0be451 | |||
c0a5f8fef0 | |||
702cf304d0 | |||
4dee8b6048 | |||
ec665e0cc1 | |||
aec3b82476 | |||
e83796b5b9 | |||
8eb69d6eda | |||
74b5124a42 | |||
b9706a180b | |||
8aeb8a9bb7 | |||
8f2e166a22 | |||
fdd91170dc | |||
c40ddb061b | |||
353d6cfc55 | |||
f37564c49c | |||
157484d94b | |||
7626c9fff7 | |||
1f55f9fc49 | |||
2554bc7ef8 | |||
7cb4100419 | |||
2d3b7eb878 | |||
4d01a78731 | |||
a03e36828a | |||
260fb65b06 | |||
9fb8526136 | |||
26fc5ff5e2 | |||
5060f0bb19 | |||
beaf6d449b | |||
4d68b508a2 | |||
cd825e386d | |||
095c8b2309 | |||
8b6eb74c58 | |||
aba437e5a2 | |||
efe3ed499b | |||
5595564a1f | |||
439761cb67 | |||
bee6c65293 | |||
10145b946b | |||
ebf4b50059 | |||
07cce3b3fc | |||
f2c19afd95 | |||
d159e70e1c | |||
ac693a2541 | |||
1e988116ce | |||
ec9e722927 | |||
4cd5e8c378 | |||
b759d5e06a | |||
1469033c1e | |||
c15fd75df7 | |||
73524e01a6 | |||
9e54e11113 | |||
01ac5f29db | |||
67a2241e32 | |||
72b6dc3de7 | |||
6f5b645995 | |||
458ad1de57 | |||
216f48b7c1 | |||
b2d1757e5a | |||
6e53eb9d5c | |||
e5ee5be9c5 | |||
bd237b2b95 | |||
d31cf766eb | |||
56d530ff04 | |||
0bbb2240f2 | |||
1c8e4dba73 | |||
4a9956c4a4 | |||
59c0e6ae32 | |||
94c30fc21e | |||
8bb3b3be20 | |||
85e3c2c5a2 | |||
4be381c597 | |||
6ff5470cf1 | |||
151dcfdef9 | |||
c282b4cb9f | |||
c426f4626c | |||
0e3c92626e | |||
5099525e24 | |||
e22b4cbb67 | |||
2b48828179 | |||
3e181362dd | |||
71fd98e39e | |||
71cd8b6d51 | |||
ad75fcbf7e | |||
f8b04a6357 | |||
d8fcbb78d3 | |||
8408bf3789 | |||
3e1185658e | |||
d778cdcd61 | |||
90b303fc03 | |||
eb86b1270d | |||
a1f0cc878b | |||
f2e2720b15 | |||
ec8cfe1591 | |||
22eac159e5 | |||
956b0c3fa7 | |||
a6427e0949 |
@ -8,7 +8,7 @@
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
|
||||
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="parser" />
|
||||
|
@ -208,17 +208,6 @@ pop_float_fac2 .proc
|
||||
jmp CONUPK
|
||||
.pend
|
||||
|
||||
pop_float_to_indexed_var .proc
|
||||
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
jsr prog8_lib.pop_index_times_5
|
||||
jsr prog8_lib.add_a_to_zpword
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp pop_float
|
||||
.pend
|
||||
|
||||
copy_float .proc
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
@ -707,13 +696,12 @@ sign_f .proc
|
||||
|
||||
|
||||
set_0_array_float .proc
|
||||
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ESTACK_LO,x
|
||||
adc P8ZP_SCRATCH_B1
|
||||
tay
|
||||
lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
@ -730,13 +718,12 @@ set_0_array_float .proc
|
||||
|
||||
|
||||
set_array_float .proc
|
||||
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ESTACK_LO,x
|
||||
adc P8ZP_SCRATCH_B1
|
||||
adc P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
bcc +
|
||||
|
@ -33,7 +33,7 @@ graphics {
|
||||
}
|
||||
word @zp d = 0
|
||||
ubyte positive_ix = true
|
||||
word @zp dx = x2-x1
|
||||
word @zp dx = x2-x1 as word
|
||||
word @zp dy = y2-y1
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
|
@ -204,23 +204,23 @@ romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- end of C64 ROM kernal routines ----
|
||||
|
@ -249,7 +249,28 @@ output .text "0000", $00 ; 0-terminated output buffer (to make printing ea
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2uword(str string @ AY) -> uword @ AY {
|
||||
|
||||
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns the unsigned byte 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)
|
||||
; TODO implement optimized custom version of this instead of simply reusing str2uword
|
||||
%asm {{
|
||||
jmp str2uword
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
|
||||
; -- returns the signed byte 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)
|
||||
; TODO implement optimized custom version of this instead of simply reusing str2word
|
||||
%asm {{
|
||||
jmp str2word
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2uword(str string @ AY) -> 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)
|
||||
@ -303,7 +324,7 @@ _result_times_10 ; (W*4 + W)*2
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2word(str string @ AY) -> word @ AY {
|
||||
asmsub str2word(str string @ AY) -> 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)
|
||||
@ -358,4 +379,77 @@ _negative .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub hex2uword(str string @ AY) -> uword @AY {
|
||||
; -- hexadecimal string with or without '$' to uword.
|
||||
; string may be in petscii or c64-screencode encoding.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'$'
|
||||
beq _skip
|
||||
cmp #7
|
||||
bcc _add_nine
|
||||
cmp #'9'
|
||||
beq _calc
|
||||
bcs _add_nine
|
||||
_calc asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
and #$0f
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
ora P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
_skip inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_stop lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
_add_nine ldy #9
|
||||
sty P8ZP_SCRATCH_B1
|
||||
bne _calc
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub bin2uword(str string @ AY) -> uword @AY {
|
||||
; -- binary string with or without '%' to uword.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop lda (P8ZP_SCRATCH_W2),y
|
||||
beq _stop
|
||||
cmp #'%'
|
||||
beq +
|
||||
asl P8ZP_SCRATCH_W1
|
||||
rol P8ZP_SCRATCH_W1+1
|
||||
and #1
|
||||
ora P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
+ inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_stop lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -78,10 +78,10 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
%asm {{
|
||||
phx
|
||||
sta P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
ldy P8ZP_SCRATCH_W2
|
||||
jsr GIVAYF ; load it as signed... correct afterwards
|
||||
lda P8ZP_SCRATCH_B1
|
||||
bpl +
|
||||
@ -98,9 +98,9 @@ _flt65536 .byte 145,0,0,0,0 ; 65536.0
|
||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
sta P8ZP_SCRATCH_W2
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
ldy P8ZP_SCRATCH_W2
|
||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||
}}
|
||||
}
|
||||
|
@ -12,48 +12,48 @@ c64 {
|
||||
|
||||
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||
|
||||
; STROUT --> use screen.print
|
||||
; CLEARSCR -> use screen.clear_screen
|
||||
; HOMECRSR -> use screen.plot
|
||||
; STROUT --> use txt.print
|
||||
; CLEARSCR -> use txt.clear_screen
|
||||
; HOMECRSR -> use txt.plot
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> ubyte @A, uword @ XY ; read/set top of memory pointer, returns number of banks in A
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use screen.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
}
|
||||
|
@ -134,13 +134,16 @@ _la lda #0 ; modified
|
||||
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
|
||||
|
||||
sub color (ubyte txtcol) {
|
||||
c64.CHROUT(color_to_charcode[txtcol & 15])
|
||||
txtcol &= 15
|
||||
c64.CHROUT(color_to_charcode[txtcol])
|
||||
}
|
||||
|
||||
sub color2 (ubyte txtcol, ubyte bgcol) {
|
||||
c64.CHROUT(color_to_charcode[bgcol & 15])
|
||||
txtcol &= 15
|
||||
bgcol &= 15
|
||||
c64.CHROUT(color_to_charcode[bgcol])
|
||||
c64.CHROUT(1) ; switch fg and bg colors
|
||||
c64.CHROUT(color_to_charcode[txtcol & 15])
|
||||
c64.CHROUT(color_to_charcode[txtcol])
|
||||
}
|
||||
|
||||
sub lowercase() {
|
||||
|
29
compiler/res/prog8lib/cx16logo.p8
Normal file
29
compiler/res/prog8lib/cx16logo.p8
Normal file
@ -0,0 +1,29 @@
|
||||
%import textio
|
||||
|
||||
cx16logo {
|
||||
sub logo_at(ubyte column, ubyte row) {
|
||||
uword strptr
|
||||
for strptr in logo_lines {
|
||||
txt.plot(column, row)
|
||||
txt.print(strptr)
|
||||
row++
|
||||
}
|
||||
}
|
||||
|
||||
sub logo() {
|
||||
uword strptr
|
||||
for strptr in logo_lines
|
||||
txt.print(strptr)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
str[] logo_lines = [
|
||||
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b\n",
|
||||
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b\n",
|
||||
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌\n",
|
||||
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132\n",
|
||||
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂\n",
|
||||
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b\n",
|
||||
"\uf101\uf130\uf13a \uf139▎\uf100"
|
||||
]
|
||||
}
|
163
compiler/res/prog8lib/diskio.p8
Normal file
163
compiler/res/prog8lib/diskio.p8
Normal file
@ -0,0 +1,163 @@
|
||||
%import textio
|
||||
%import syslib
|
||||
|
||||
; Note: this code is compatible with C64 and CX16.
|
||||
|
||||
diskio {
|
||||
|
||||
|
||||
sub directory(ubyte drivenumber) -> byte {
|
||||
; -- Shows the directory contents of disk drive 8-11 (provide as argument).
|
||||
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
void c64.OPEN() ; open 1,8,0,"$"
|
||||
if_cs
|
||||
goto io_error
|
||||
void c64.CHKIN(1) ; use #1 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
repeat 4 {
|
||||
void c64.CHRIN() ; skip the 4 prologue bytes
|
||||
}
|
||||
|
||||
; while not key pressed / EOF encountered, read data.
|
||||
ubyte status = c64.READST()
|
||||
while not status {
|
||||
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
|
||||
txt.chrout(' ')
|
||||
ubyte @zp char
|
||||
do {
|
||||
char = c64.CHRIN()
|
||||
txt.chrout(char)
|
||||
} until char==0
|
||||
txt.chrout('\n')
|
||||
void c64.CHRIN() ; skip 2 bytes
|
||||
void c64.CHRIN()
|
||||
status = c64.READST()
|
||||
void c64.STOP()
|
||||
if_nz
|
||||
break
|
||||
}
|
||||
|
||||
io_error:
|
||||
status = c64.READST()
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(1)
|
||||
|
||||
if status and status != 64 { ; 64=end of file
|
||||
txt.print("\ni/o error, status: ")
|
||||
txt.print_ub(status)
|
||||
txt.chrout('\n')
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
sub status(ubyte drivenumber) {
|
||||
; -- display the disk drive's current status message
|
||||
c64.SETNAM(0, $0000)
|
||||
c64.SETLFS(15, drivenumber, 15)
|
||||
void c64.OPEN() ; open 15,8,15
|
||||
if_cs
|
||||
goto io_error
|
||||
void c64.CHKIN(15) ; use #15 as input channel
|
||||
if_cs
|
||||
goto io_error
|
||||
|
||||
while not c64.READST()
|
||||
txt.chrout(c64.CHRIN())
|
||||
|
||||
io_error:
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
c64.CLOSE(15)
|
||||
}
|
||||
|
||||
|
||||
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> byte {
|
||||
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
uword end_address = address + size
|
||||
|
||||
%asm {{
|
||||
lda address
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda address+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #<P8ZP_SCRATCH_W1
|
||||
ldx end_address
|
||||
ldy end_address+1
|
||||
jsr c64.SAVE
|
||||
php
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
plp
|
||||
}}
|
||||
|
||||
if_cc
|
||||
return c64.READST()==0
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||
c64.SETNAM(strlen(filenameptr), filenameptr)
|
||||
ubyte secondary = 1
|
||||
uword end_of_load = 0
|
||||
if address_override
|
||||
secondary = 0
|
||||
c64.SETLFS(1, drivenumber, secondary)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #0
|
||||
ldx address_override
|
||||
ldy address_override+1
|
||||
jsr c64.LOAD
|
||||
bcs +
|
||||
stx end_of_load
|
||||
sty end_of_load+1
|
||||
+ ldx P8ZP_SCRATCH_REG
|
||||
}}
|
||||
|
||||
if end_of_load
|
||||
return end_of_load - address_override
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
str filename = "0:??????????????????????????????????????"
|
||||
|
||||
sub delete(ubyte drivenumber, uword filenameptr) {
|
||||
; -- delete a file on the drive
|
||||
ubyte flen = strlen(filenameptr)
|
||||
filename[0] = 's'
|
||||
filename[1] = ':'
|
||||
memcopy(filenameptr, &filename+2, flen+1)
|
||||
c64.SETNAM(flen+2, filename)
|
||||
c64.SETLFS(1, drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
}
|
||||
|
||||
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
|
||||
; -- rename a file on the drive
|
||||
ubyte flen_old = strlen(oldfileptr)
|
||||
ubyte flen_new = strlen(newfileptr)
|
||||
filename[0] = 'r'
|
||||
filename[1] = ':'
|
||||
memcopy(newfileptr, &filename+2, flen_new)
|
||||
ubyte fis_ix = flen_new+2 ; TODO is temp var for array indexing
|
||||
filename[fis_ix] = '='
|
||||
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
|
||||
c64.SETNAM(3+flen_new+flen_old, filename)
|
||||
c64.SETLFS(1, drivenumber, 15)
|
||||
void c64.OPEN()
|
||||
c64.CLRCHN()
|
||||
c64.CLOSE(1)
|
||||
}
|
||||
}
|
@ -781,7 +781,7 @@ mul_byte_3 .proc
|
||||
sta P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
clc
|
||||
adc P8P_P8ZP_SCRATCH_REG
|
||||
adc P8ZP_SCRATCH_REG
|
||||
rts
|
||||
.pend
|
||||
|
||||
@ -1469,3 +1469,22 @@ shift_right_w_3 .proc
|
||||
jmp shift_right_w_7._shift3
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
; support for bit shifting that is too large to be unrolled:
|
||||
|
||||
lsr_byte_A .proc
|
||||
; -- lsr signed byte in A times the value in Y (assume >0)
|
||||
cmp #0
|
||||
bmi _negative
|
||||
- lsr a
|
||||
dey
|
||||
bne -
|
||||
rts
|
||||
_negative lsr a
|
||||
ora #$80
|
||||
dey
|
||||
bne _negative
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
@ -40,16 +40,6 @@ add_a_to_zpword .proc
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
pop_index_times_5 .proc
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ESTACK_LO,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
neg_b .proc
|
||||
lda #0
|
||||
sec
|
||||
@ -682,6 +672,7 @@ func_read_flags .proc
|
||||
|
||||
|
||||
func_sqrt16 .proc
|
||||
; TODO is this one faster? http://6502org.wikidot.com/software-math-sqrt
|
||||
lda P8ESTACK_LO+1,x
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda P8ESTACK_HI+1,x
|
||||
@ -1976,7 +1967,7 @@ ror2_array_uw .proc
|
||||
|
||||
|
||||
strcpy .proc
|
||||
; copy a string (0-terminated) from A/Y to (ZPWORD1)
|
||||
; copy a string (must be 0-terminated) from A/Y to (P8ZP_SCRATCH_W1)
|
||||
; it is assumed the target string is large enough.
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
@ -1989,6 +1980,38 @@ strcpy .proc
|
||||
.pend
|
||||
|
||||
|
||||
strcmp_mem .proc
|
||||
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
|
||||
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
_loop ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
bne +
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
bne _return_minusone
|
||||
beq _return
|
||||
+ lda (P8ZP_SCRATCH_W2),y
|
||||
sec
|
||||
sbc (P8ZP_SCRATCH_W1),y
|
||||
bmi _return_one
|
||||
bne _return_minusone
|
||||
inc P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ inc P8ZP_SCRATCH_W2
|
||||
bne _loop
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
bne _loop
|
||||
_return_one
|
||||
lda #1
|
||||
_return rts
|
||||
_return_minusone
|
||||
lda #-1
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_leftstr .proc
|
||||
; leftstr(source, target, length) with params on stack
|
||||
inx
|
||||
@ -2097,3 +2120,18 @@ _startloop dey
|
||||
rts
|
||||
|
||||
.pend
|
||||
|
||||
|
||||
func_strcmp .proc
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda P8ESTACK_HI,x
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda P8ESTACK_HI+1,x
|
||||
tay
|
||||
lda P8ESTACK_LO+1,x
|
||||
jsr strcmp_mem
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
@ -5,5 +5,5 @@
|
||||
; indent format: TABS, size=8
|
||||
|
||||
prog8_lib {
|
||||
%asminclude "library:prog8lib.asm", ""
|
||||
%asminclude "library:prog8_lib.asm", ""
|
||||
}
|
@ -1 +1 @@
|
||||
4.4
|
||||
4.6
|
||||
|
@ -38,6 +38,7 @@ private fun compileMain(args: Array<String>) {
|
||||
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
||||
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
|
||||
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||
val slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation")
|
||||
val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
|
||||
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
|
||||
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
|
||||
@ -62,7 +63,7 @@ private fun compileMain(args: Array<String>) {
|
||||
println("Continuous watch mode active. Main module: $filepath")
|
||||
|
||||
try {
|
||||
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
|
||||
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||
println("Imported files (now watching:)")
|
||||
for (importedFile in compilationResult.importedFiles) {
|
||||
print(" ")
|
||||
@ -87,7 +88,7 @@ private fun compileMain(args: Array<String>) {
|
||||
val filepath = pathFrom(filepathRaw).normalize()
|
||||
val compilationResult: CompilationResult
|
||||
try {
|
||||
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
|
||||
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
|
||||
if(!compilationResult.success)
|
||||
exitProcess(1)
|
||||
} catch (x: ParsingFailedError) {
|
||||
|
@ -121,7 +121,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
output(datatypeString(decl.datatype))
|
||||
if(decl.arraysize!=null) {
|
||||
decl.arraysize!!.index.accept(this)
|
||||
decl.arraysize!!.indexNum?.accept(this)
|
||||
decl.arraysize!!.indexVar?.accept(this)
|
||||
}
|
||||
if(decl.isArray)
|
||||
output("]")
|
||||
@ -352,9 +353,10 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
arrayIndexedExpression.identifier.accept(this)
|
||||
arrayIndexedExpression.arrayvar.accept(this)
|
||||
output("[")
|
||||
arrayIndexedExpression.arrayspec.index.accept(this)
|
||||
arrayIndexedExpression.indexer.indexNum?.accept(this)
|
||||
arrayIndexedExpression.indexer.indexVar?.accept(this)
|
||||
output("]")
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,18 @@ interface Node {
|
||||
throw FatalAstException("scope missing from $this")
|
||||
}
|
||||
|
||||
fun definingBlock(): Block {
|
||||
if(this is Block)
|
||||
return this
|
||||
return findParentNode<Block>(this)!!
|
||||
}
|
||||
|
||||
fun containingStatement(): Statement {
|
||||
if(this is Statement)
|
||||
return this
|
||||
return findParentNode<Statement>(this)!!
|
||||
}
|
||||
|
||||
fun replaceChildNode(node: Node, replacement: Node)
|
||||
}
|
||||
|
||||
@ -44,11 +56,20 @@ interface IFunctionCall {
|
||||
var args: MutableList<Expression>
|
||||
}
|
||||
|
||||
|
||||
class AsmGenInfo {
|
||||
var usedAutoArrayIndexerForStatements = mutableMapOf<String, MutableSet<Statement>>()
|
||||
var usedRegsaveA = false
|
||||
var usedRegsaveX = false
|
||||
var usedRegsaveY = false
|
||||
}
|
||||
|
||||
interface INameScope {
|
||||
val name: String
|
||||
val position: Position
|
||||
val statements: MutableList<Statement>
|
||||
val parent: Node
|
||||
val asmGenInfo: AsmGenInfo
|
||||
|
||||
fun linkParents(parent: Node)
|
||||
|
||||
@ -163,7 +184,6 @@ interface INameScope {
|
||||
}
|
||||
|
||||
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
||||
fun containsNoVars() = statements.all { it !is VarDecl }
|
||||
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
||||
|
||||
fun remove(stmt: Statement) {
|
||||
@ -203,6 +223,14 @@ interface INameScope {
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
fun indexOfChild(stmt: Statement): Int {
|
||||
val idx = statements.indexOfFirst { it===stmt }
|
||||
if(idx>=0)
|
||||
return idx
|
||||
else
|
||||
throw FatalAstException("attempt to find a non-child")
|
||||
}
|
||||
}
|
||||
|
||||
interface IAssignable {
|
||||
@ -236,7 +264,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||
override val position: Position = Position.DUMMY
|
||||
override var parent: Node
|
||||
get() = throw FatalAstException("program has no parent")
|
||||
set(value) = throw FatalAstException("can't set parent of program")
|
||||
set(_) = throw FatalAstException("can't set parent of program")
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
modules.forEach {
|
||||
@ -260,10 +288,14 @@ class Module(override val name: String,
|
||||
|
||||
override lateinit var parent: Node
|
||||
lateinit var program: Program
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
val importedBy = mutableListOf<Module>()
|
||||
val imports = mutableSetOf<Module>()
|
||||
|
||||
var loadAddress: Int = 0 // can be set with the %address directive
|
||||
val loadAddress: Int by lazy {
|
||||
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
|
||||
address
|
||||
}
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -288,8 +320,9 @@ class Module(override val name: String,
|
||||
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
override val name = "<<<global>>>"
|
||||
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||
override val statements = mutableListOf<Statement>()
|
||||
override val statements = mutableListOf<Statement>() // not used
|
||||
override var parent: Node = ParentSentinel
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
modules.forEach { it.linkParents(this) }
|
||||
@ -342,6 +375,7 @@ object BuiltinFunctionScopePlaceholder : INameScope {
|
||||
override val position = Position("<<placeholder>>", 0, 0, 0)
|
||||
override var statements = mutableListOf<Statement>()
|
||||
override var parent: Node = ParentSentinel
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
override fun linkParents(parent: Node) {}
|
||||
}
|
||||
|
||||
|
@ -470,7 +470,6 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||
litval.stringliteral()!=null -> litval.stringliteral().toAst()
|
||||
litval.charliteral()!=null -> {
|
||||
try {
|
||||
val cc=litval.charliteral()
|
||||
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
|
||||
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
|
||||
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
|
||||
@ -647,7 +646,20 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
|
||||
)
|
||||
}
|
||||
|
||||
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
|
||||
internal fun escape(str: String): String {
|
||||
val es = str.map {
|
||||
when(it) {
|
||||
'\t' -> "\\t"
|
||||
'\n' -> "\\n"
|
||||
'\r' -> "\\r"
|
||||
'"' -> "\\\""
|
||||
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
|
||||
in '\u0000'..'\u00ff' -> it.toString()
|
||||
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
}
|
||||
return es.joinToString("")
|
||||
}
|
||||
|
||||
internal fun unescape(str: String, position: Position): String {
|
||||
val result = mutableListOf<Char>()
|
||||
@ -661,9 +673,15 @@ internal fun unescape(str: String, position: Position): String {
|
||||
'n' -> '\n'
|
||||
'r' -> '\r'
|
||||
'"' -> '"'
|
||||
'\'' -> '\''
|
||||
'u' -> {
|
||||
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
|
||||
}
|
||||
'x' -> {
|
||||
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
|
||||
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
|
||||
(0x8000 + hex).toChar()
|
||||
}
|
||||
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
|
||||
})
|
||||
} else {
|
||||
|
@ -39,16 +39,20 @@ enum class DataType {
|
||||
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
|
||||
|
||||
infix fun largerThan(other: DataType) =
|
||||
when(this) {
|
||||
in ByteDatatypes -> false
|
||||
in WordDatatypes -> other in ByteDatatypes
|
||||
when {
|
||||
this == other -> false
|
||||
this in ByteDatatypes -> false
|
||||
this in WordDatatypes -> other in ByteDatatypes
|
||||
this==STR && other==UWORD || this==UWORD && other==STR -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
infix fun equalsSize(other: DataType) =
|
||||
when(this) {
|
||||
in ByteDatatypes -> other in ByteDatatypes
|
||||
in WordDatatypes -> other in WordDatatypes
|
||||
when {
|
||||
this == other -> true
|
||||
this in ByteDatatypes -> other in ByteDatatypes
|
||||
this in WordDatatypes -> other in WordDatatypes
|
||||
this==STR && other==UWORD || this==UWORD && other==STR -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package prog8.ast.base
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.*
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
|
||||
@ -18,8 +19,8 @@ internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
|
||||
fixer.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements() {
|
||||
val reorder = StatementReorderer(this)
|
||||
internal fun Program.reorderStatements(errors: ErrorReporter) {
|
||||
val reorder = StatementReorderer(this, errors)
|
||||
reorder.visit(this)
|
||||
reorder.applyModifications()
|
||||
}
|
||||
@ -41,12 +42,6 @@ internal fun Module.checkImportedValid() {
|
||||
imr.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.checkRecursion(errors: ErrorReporter) {
|
||||
val checker = AstRecursionChecker(namespace, errors)
|
||||
checker.visit(this)
|
||||
checker.processMessages(name)
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||
|
||||
val checker2 = AstIdentifiersChecker(this, errors)
|
||||
@ -56,6 +51,9 @@ internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||
val transforms = AstVariousTransforms(this)
|
||||
transforms.visit(this)
|
||||
transforms.applyModifications()
|
||||
val lit2decl = LiteralsToAutoVars(this)
|
||||
lit2decl.visit(this)
|
||||
lit2decl.applyModifications()
|
||||
}
|
||||
|
||||
if (modules.map { it.name }.toSet().size != modules.size) {
|
||||
@ -68,3 +66,37 @@ internal fun Program.variousCleanups() {
|
||||
process.visit(this)
|
||||
process.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.moveMainAndStartToFirst() {
|
||||
// the module containing the program entrypoint is moved to the first in the sequence.
|
||||
// the "main" block containing the entrypoint is moved to the top in there,
|
||||
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
|
||||
|
||||
val directives = modules[0].statements.filterIsInstance<Directive>()
|
||||
val start = this.entrypoint()
|
||||
if(start!=null) {
|
||||
val mod = start.definingModule()
|
||||
val block = start.definingBlock()
|
||||
if(!modules.remove(mod))
|
||||
throw FatalAstException("module wrong")
|
||||
modules.add(0, mod)
|
||||
mod.remove(block)
|
||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
mod.statements.add(block)
|
||||
else
|
||||
mod.statements.add(afterDirective, block)
|
||||
block.remove(start)
|
||||
afterDirective = block.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
block.statements.add(start)
|
||||
else
|
||||
block.statements.add(afterDirective, start)
|
||||
|
||||
// overwrite the directives in the module containing the entrypoint
|
||||
for(directive in directives) {
|
||||
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
|
||||
modules[0].statements.add(0, directive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ sealed class Expression: Node {
|
||||
&& other.left isSameAs left
|
||||
&& other.right isSameAs right)
|
||||
is ArrayIndexedExpression -> {
|
||||
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
||||
&& other.arrayspec.index isSameAs arrayspec.index)
|
||||
(other is ArrayIndexedExpression && other.arrayvar.nameInSource == arrayvar.nameInSource
|
||||
&& other.indexer isSameAs indexer)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
|
||||
@ -232,20 +232,19 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
val arrayspec: ArrayIndex,
|
||||
class ArrayIndexedExpression(var arrayvar: IdentifierReference,
|
||||
val indexer: ArrayIndex,
|
||||
override val position: Position) : Expression(), IAssignable {
|
||||
override lateinit var parent: Node
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
identifier.linkParents(this)
|
||||
arrayspec.linkParents(this)
|
||||
arrayvar.linkParents(this)
|
||||
indexer.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===identifier -> identifier = replacement as IdentifierReference
|
||||
node===arrayspec.index -> arrayspec.index = replacement as Expression
|
||||
node===arrayvar -> arrayvar = replacement as IdentifierReference
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
@ -255,10 +254,10 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifier(vararg scopedName: String) = identifier.referencesIdentifier(*scopedName)
|
||||
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val target = identifier.targetStatement(program.namespace)
|
||||
val target = arrayvar.targetStatement(program.namespace)
|
||||
if (target is VarDecl) {
|
||||
return when (target.datatype) {
|
||||
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
|
||||
@ -270,7 +269,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
|
||||
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
|
||||
}
|
||||
}
|
||||
|
||||
@ -872,6 +871,12 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
return InferredTypes.void() // no return value
|
||||
if(stmt.returntypes.size==1)
|
||||
return InferredTypes.knownFor(stmt.returntypes[0])
|
||||
|
||||
// multiple return values. Can occur for asmsub routines. If there is exactly one register return value, take that.
|
||||
val numRegisterReturns = stmt.asmReturnvaluesRegisters.count { it.registerOrPair!=null }
|
||||
if(numRegisterReturns==1)
|
||||
return InferredTypes.InferredType.known(DataType.UBYTE)
|
||||
|
||||
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
|
||||
}
|
||||
else -> return InferredTypes.unknown()
|
||||
|
@ -120,11 +120,6 @@ internal class AstChecker(private val program: Program,
|
||||
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
|
||||
errors.err("for loop requires a variable to loop with", forLoop.position)
|
||||
} else {
|
||||
|
||||
fun checkLoopRangeValues() {
|
||||
|
||||
}
|
||||
|
||||
when (loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
|
||||
@ -346,17 +341,15 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
// 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(program.namespace)
|
||||
if (stmt is Subroutine && stmt.isAsmSubroutine) {
|
||||
if(stmt.returntypes.size>1)
|
||||
errors.err("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position)
|
||||
else {
|
||||
val idt = assignment.target.inferType(program, assignment)
|
||||
if(!idt.isKnown || stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
if (stmt is Subroutine) {
|
||||
val idt = assignment.target.inferType(program, assignment)
|
||||
if(!idt.isKnown) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
if(stmt.returntypes.size <= 1 && stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -386,7 +379,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
val targetDt = assignment.target.inferType(program, assignment)
|
||||
if(assignment.value.inferType(program) != targetDt) {
|
||||
val valueDt = assignment.value.inferType(program)
|
||||
if(valueDt.isKnown && valueDt != targetDt) {
|
||||
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
|
||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||
else
|
||||
@ -446,12 +440,8 @@ internal class AstChecker(private val program: Program,
|
||||
checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal)
|
||||
} else {
|
||||
val sourceDatatype = assignment.value.inferType(program)
|
||||
if (!sourceDatatype.isKnown) {
|
||||
if (assignment.value is FunctionCall) {
|
||||
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
|
||||
if (targetStmt != null)
|
||||
errors.err("function call doesn't return a suitable value to use in assignment", assignment.value.position)
|
||||
} else
|
||||
if (sourceDatatype.isUnknown) {
|
||||
if (assignment.value !is FunctionCall)
|
||||
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
|
||||
} else {
|
||||
checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
|
||||
@ -469,6 +459,7 @@ internal class AstChecker(private val program: Program,
|
||||
else {
|
||||
if(variable.datatype !in ArrayDatatypes
|
||||
&& variable.type!=VarDeclType.MEMORY
|
||||
&& variable.struct == null
|
||||
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
|
||||
errors.err("invalid pointer-of operand type", addressOf.position)
|
||||
}
|
||||
@ -479,7 +470,7 @@ internal class AstChecker(private val program: Program,
|
||||
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true)
|
||||
err("recursive var declaration")
|
||||
|
||||
// CONST can only occur on simple types (byte, word, float)
|
||||
@ -632,6 +623,14 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// string assignment is not supported in a vard
|
||||
if(decl.datatype==DataType.STR) {
|
||||
if(decl.value==null)
|
||||
err("string var must be initialized with a string literal")
|
||||
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
|
||||
err("string var can only be initialized with a string literal")
|
||||
}
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
@ -753,8 +752,13 @@ internal class AstChecker(private val program: Program,
|
||||
return e is StringLiteralValue
|
||||
}
|
||||
|
||||
if(!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
|
||||
errors.err("array literal contains invalid types", array.position)
|
||||
if(array.parent is VarDecl) {
|
||||
if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
|
||||
errors.err("array literal for variable initialization contains invalid types", array.position)
|
||||
} else if(array.parent is ForLoop) {
|
||||
if (!array.value.all { it.constValue(program) != null })
|
||||
errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position)
|
||||
}
|
||||
|
||||
super.visit(array)
|
||||
}
|
||||
@ -783,6 +787,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(expr: BinaryExpression) {
|
||||
super.visit(expr)
|
||||
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
@ -822,13 +828,12 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt !in NumericDatatypes)
|
||||
errors.err("left operand is not numeric", expr.left.position)
|
||||
if(rightDt!in NumericDatatypes)
|
||||
errors.err("right operand is not numeric", expr.right.position)
|
||||
if(leftDt !in NumericDatatypes && leftDt != DataType.STR)
|
||||
errors.err("left operand is not numeric or str", expr.left.position)
|
||||
if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
|
||||
errors.err("right operand is not numeric or str", expr.right.position)
|
||||
if(leftDt!=rightDt)
|
||||
errors.err("left and right operands aren't the same type", expr.left.position)
|
||||
super.visit(expr)
|
||||
}
|
||||
|
||||
override fun visit(typecast: TypecastExpression) {
|
||||
@ -888,7 +893,29 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program)
|
||||
if(error!=null)
|
||||
errors.err(error, functionCall.args.first().position)
|
||||
errors.err(error, functionCall.position)
|
||||
|
||||
// check the functions that return multiple returnvalues.
|
||||
val stmt = functionCall.target.targetStatement(program.namespace)
|
||||
if (stmt is Subroutine) {
|
||||
if (stmt.returntypes.size > 1) {
|
||||
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
|
||||
// asmsub routines can have multiple return values, for instance in 2 different registers.
|
||||
// It's not (yet) possible to handle these multiple return values because assignments
|
||||
// are only to a single unique target at the same time.
|
||||
// EXCEPTION:
|
||||
// if the asmsub returns multiple values and one of them is via a status register bit,
|
||||
// it *is* possible to handle them by just actually assigning the register value and
|
||||
// dealing with the status bit as just being that, the status bit after the call.
|
||||
val (returnRegisters, returnStatusflags) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
|
||||
if (returnRegisters.isEmpty() || returnRegisters.size == 1) {
|
||||
if (returnStatusflags.any())
|
||||
errors.warn("this asmsub also has one or more return 'values' in one of the status flags", functionCall.position)
|
||||
} else {
|
||||
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCall.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(functionCall)
|
||||
}
|
||||
@ -981,7 +1008,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
} else if(postIncrDecr.target.arrayindexed != null) {
|
||||
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
|
||||
val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program.namespace)
|
||||
if(target==null) {
|
||||
errors.err("undefined symbol", postIncrDecr.position)
|
||||
}
|
||||
@ -996,32 +1023,38 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
val target = arrayIndexedExpression.identifier.targetStatement(program.namespace)
|
||||
val target = arrayIndexedExpression.arrayvar.targetStatement(program.namespace)
|
||||
if(target is VarDecl) {
|
||||
if(target.datatype !in IterableDatatypes)
|
||||
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
|
||||
val arraysize = target.arraysize?.constIndex()
|
||||
if(arraysize!=null) {
|
||||
// check out of bounds
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
|
||||
val index = arrayIndexedExpression.indexer.constIndex()
|
||||
if(index!=null && (index<0 || index>=arraysize))
|
||||
errors.err("array index out of bounds", arrayIndexedExpression.arrayspec.position)
|
||||
errors.err("array index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
} else if(target.datatype == DataType.STR) {
|
||||
if(target.value is StringLiteralValue) {
|
||||
// check string lengths for non-memory mapped strings
|
||||
val stringLen = (target.value as StringLiteralValue).value.length
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
|
||||
val index = arrayIndexedExpression.indexer.constIndex()
|
||||
if (index != null && (index < 0 || index >= stringLen))
|
||||
errors.err("index out of bounds", arrayIndexedExpression.arrayspec.position)
|
||||
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
|
||||
}
|
||||
}
|
||||
} else
|
||||
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
|
||||
|
||||
// check index value 0..255
|
||||
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE)
|
||||
val dtxNum = arrayIndexedExpression.indexer.indexNum?.inferType(program)?.typeOrElse(DataType.STRUCT)
|
||||
if(dtxNum!=null && dtxNum != DataType.UBYTE && dtxNum != DataType.BYTE)
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
val dtxVar = arrayIndexedExpression.indexer.indexVar?.inferType(program)?.typeOrElse(DataType.STRUCT)
|
||||
if(dtxVar!=null && dtxVar != DataType.UBYTE && dtxVar != DataType.BYTE)
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
|
||||
if(arrayIndexedExpression.indexer.origExpression!=null)
|
||||
throw FatalAstException("array indexer should have been replaced with a temp var @ ${arrayIndexedExpression.indexer.position}")
|
||||
|
||||
super.visit(arrayIndexedExpression)
|
||||
}
|
||||
@ -1126,10 +1159,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>256)
|
||||
return err("byte array length must be 1-256")
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.type !in IntegerDatatypes)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX!!.number.toInt()
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
return true
|
||||
@ -1148,10 +1178,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>128)
|
||||
return err("word array length must be 1-128")
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.type !in IntegerDatatypes)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX!!.number.toInt()
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
return true
|
||||
@ -1170,10 +1197,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize < 1 || arraySpecSize>51)
|
||||
return err("float array length must be 1-51")
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.type !in IntegerDatatypes)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX!!.number.toInt()
|
||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||
if (arraySize != expectedSize)
|
||||
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||
} else
|
||||
|
@ -80,6 +80,11 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
if (existing != null && existing !== decl)
|
||||
nameError(decl.name, decl.position, existing)
|
||||
|
||||
if(decl.definingBlock().name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingBlock())
|
||||
if(decl.definingSubroutine()?.name==decl.name)
|
||||
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
@ -143,8 +148,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
}
|
||||
|
||||
override fun visit(string: StringLiteralValue) {
|
||||
if (string.value.length !in 1..255)
|
||||
errors.err("string literal length must be between 1 and 255", string.position)
|
||||
if (string.value.length > 255)
|
||||
errors.err("string literal length max is 255", string.position)
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
@ -1,118 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
|
||||
|
||||
internal class AstRecursionChecker(private val namespace: INameScope,
|
||||
private val errors: ErrorReporter) : IAstVisitor {
|
||||
private val callGraph = DirectedGraph<INameScope>()
|
||||
|
||||
fun processMessages(modulename: String) {
|
||||
val cycle = callGraph.checkForCycle()
|
||||
if(cycle.isEmpty())
|
||||
return
|
||||
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val scope = functionCallStatement.definingScope()
|
||||
val targetStatement = functionCallStatement.target.targetStatement(namespace)
|
||||
if(targetStatement!=null) {
|
||||
val targetScope = when (targetStatement) {
|
||||
is Subroutine -> targetStatement
|
||||
else -> targetStatement.definingScope()
|
||||
}
|
||||
callGraph.add(scope, targetScope)
|
||||
}
|
||||
super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val scope = functionCall.definingScope()
|
||||
val targetStatement = functionCall.target.targetStatement(namespace)
|
||||
if(targetStatement!=null) {
|
||||
val targetScope = when (targetStatement) {
|
||||
is Subroutine -> targetStatement
|
||||
else -> targetStatement.definingScope()
|
||||
}
|
||||
callGraph.add(scope, targetScope)
|
||||
}
|
||||
super.visit(functionCall)
|
||||
}
|
||||
|
||||
private class DirectedGraph<VT> {
|
||||
private val graph = mutableMapOf<VT, MutableSet<VT>>()
|
||||
private var uniqueVertices = mutableSetOf<VT>()
|
||||
val numVertices : Int
|
||||
get() = uniqueVertices.size
|
||||
|
||||
fun add(from: VT, to: VT) {
|
||||
var targets = graph[from]
|
||||
if(targets==null) {
|
||||
targets = mutableSetOf()
|
||||
graph[from] = targets
|
||||
}
|
||||
targets.add(to)
|
||||
uniqueVertices.add(from)
|
||||
uniqueVertices.add(to)
|
||||
}
|
||||
|
||||
fun print() {
|
||||
println("#vertices: $numVertices")
|
||||
graph.forEach { (from, to) ->
|
||||
println("$from CALLS:")
|
||||
to.forEach { println(" $it") }
|
||||
}
|
||||
val cycle = checkForCycle()
|
||||
if(cycle.isNotEmpty()) {
|
||||
println("CYCLIC! $cycle")
|
||||
}
|
||||
}
|
||||
|
||||
fun checkForCycle(): MutableList<VT> {
|
||||
val visited = uniqueVertices.associateWith { false }.toMutableMap()
|
||||
val recStack = uniqueVertices.associateWith { false }.toMutableMap()
|
||||
val cycle = mutableListOf<VT>()
|
||||
for(node in uniqueVertices) {
|
||||
if(isCyclicUntil(node, visited, recStack, cycle))
|
||||
return cycle
|
||||
}
|
||||
return mutableListOf()
|
||||
}
|
||||
|
||||
private fun isCyclicUntil(node: VT,
|
||||
visited: MutableMap<VT, Boolean>,
|
||||
recStack: MutableMap<VT, Boolean>,
|
||||
cycleNodes: MutableList<VT>): Boolean {
|
||||
|
||||
if(recStack[node]==true) return true
|
||||
if(visited[node]==true) return false
|
||||
|
||||
// mark current node as visited and add to recursion stack
|
||||
visited[node] = true
|
||||
recStack[node] = true
|
||||
|
||||
// recurse for all neighbours
|
||||
val neighbors = graph[node]
|
||||
if(neighbors!=null) {
|
||||
for (neighbour in neighbors) {
|
||||
if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) {
|
||||
cycleNodes.add(node)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pop node from recursion stack
|
||||
recStack[node] = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -47,78 +47,59 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
when {
|
||||
expr.left is StringLiteralValue ->
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
|
||||
parent
|
||||
))
|
||||
expr.right is StringLiteralValue ->
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
|
||||
parent
|
||||
))
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftStr = expr.left as? StringLiteralValue
|
||||
val rightStr = expr.right as? StringLiteralValue
|
||||
if(expr.operator == "+") {
|
||||
val concatenatedString = concatString(expr)
|
||||
if(concatenatedString!=null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl) {
|
||||
// replace the literal string by a identifier reference to a new local vardecl
|
||||
val vardecl = VarDecl.createAuto(string)
|
||||
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(string, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
|
||||
)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast!=array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
else if(expr.operator == "*") {
|
||||
if (leftStr!=null) {
|
||||
val amount = expr.right.constValue(program)
|
||||
if(amount!=null) {
|
||||
val string = leftStr.value.repeat(amount.number.toInt())
|
||||
val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if(litval2!=null && litval2!=array) {
|
||||
val vardecl2 = VarDecl.createAuto(litval2)
|
||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope() as Node)
|
||||
)
|
||||
else if (rightStr!=null) {
|
||||
val amount = expr.right.constValue(program)
|
||||
if(amount!=null) {
|
||||
val string = rightStr.value.repeat(amount.number.toInt())
|
||||
val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
||||
val constvalue = operand.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if (expr.operator == "*") {
|
||||
// repeat a string a number of times
|
||||
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
|
||||
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
|
||||
val rightStrval = expr.right as? StringLiteralValue
|
||||
val leftStrval = expr.left as? StringLiteralValue
|
||||
return when {
|
||||
expr.operator!="+" -> null
|
||||
expr.left is BinaryExpression && rightStrval!=null -> {
|
||||
val subStrVal = concatString(expr.left as BinaryExpression)
|
||||
if(subStrVal==null)
|
||||
null
|
||||
else
|
||||
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position)
|
||||
}
|
||||
expr.right is BinaryExpression && leftStrval!=null -> {
|
||||
val subStrVal = concatString(expr.right as BinaryExpression)
|
||||
if(subStrVal==null)
|
||||
null
|
||||
else
|
||||
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position)
|
||||
}
|
||||
leftStrval!=null && rightStrval!=null -> {
|
||||
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
if(expr.operator == "+" && operand is StringLiteralValue) {
|
||||
// concatenate two strings
|
||||
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
}
|
||||
|
@ -9,78 +9,58 @@ import prog8.ast.statements.*
|
||||
interface IAstModification {
|
||||
fun perform()
|
||||
|
||||
class Remove(val node: Node, val parent: Node) : IAstModification {
|
||||
class Remove(val node: Node, val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
|
||||
throw FatalAstException("attempt to remove non-existing node $node")
|
||||
} else {
|
||||
throw FatalAstException("parent of a remove modification is not an INameScope")
|
||||
}
|
||||
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
|
||||
throw FatalAstException("attempt to remove non-existing node $node")
|
||||
}
|
||||
}
|
||||
|
||||
class SetExpression(val setter: (newExpr: Expression) -> Unit, val newExpr: Expression, val parent: Node) : IAstModification {
|
||||
class SetExpression(private val setter: (newExpr: Expression) -> Unit, private val newExpr: Expression, private val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
setter(newExpr)
|
||||
newExpr.linkParents(parent)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertFirst(val stmt: Statement, val parent: Node) : IAstModification {
|
||||
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
parent.statements.add(0, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
parent.statements.add(0, stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
|
||||
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
parent.statements.add(stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
parent.statements.add(stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
val idx = parent.statements.indexOfFirst { it===after } + 1
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
val idx = parent.statements.indexOfFirst { it===after } + 1
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class InsertBefore(val before: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
val idx = parent.statements.indexOfFirst { it===before }
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
val idx = parent.statements.indexOfFirst { it===before }
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent as Node)
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
|
||||
class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
parent.replaceChildNode(node, replacement)
|
||||
replacement.linkParents(parent)
|
||||
}
|
||||
}
|
||||
|
||||
class SwapOperands(val expr: BinaryExpression): IAstModification {
|
||||
class SwapOperands(private val expr: BinaryExpression): IAstModification {
|
||||
override fun perform() {
|
||||
require(expr.operator in associativeOperators)
|
||||
val tmp = expr.left
|
||||
@ -363,8 +343,8 @@ abstract class AstWalker {
|
||||
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
|
||||
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||
arrayIndexedExpression.identifier.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.arrayspec.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
|
||||
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
|
||||
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||
}
|
||||
|
||||
|
@ -125,8 +125,8 @@ interface IAstVisitor {
|
||||
}
|
||||
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
arrayIndexedExpression.identifier.accept(this)
|
||||
arrayIndexedExpression.arrayspec.accept(this)
|
||||
arrayIndexedExpression.arrayvar.accept(this)
|
||||
arrayIndexedExpression.indexer.accept(this)
|
||||
}
|
||||
|
||||
fun visit(assignTarget: AssignTarget) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.statements.Directive
|
||||
|
||||
@ -14,7 +15,7 @@ internal class ImportedModuleDirectiveRemover: AstWalker() {
|
||||
|
||||
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
|
||||
if(directive.directive in moduleLevelDirectives) {
|
||||
return listOf(IAstModification.Remove(directive, parent))
|
||||
return listOf(IAstModification.Remove(directive, parent as INameScope))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
53
compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt
Normal file
53
compiler/src/prog8/ast/processing/LiteralsToAutoVars.kt
Normal file
@ -0,0 +1,53 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||
// replace the literal string by a identifier reference to a new local vardecl
|
||||
val vardecl = VarDecl.createAuto(string)
|
||||
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(string, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl, string.definingScope())
|
||||
)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast !== array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
}
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if(litval2!=null) {
|
||||
val vardecl2 = VarDecl.createAuto(litval2)
|
||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -6,12 +6,12 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
// Reorders the statements in a way the compiler needs.
|
||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||
// - library blocks are put last.
|
||||
// - blocks are ordered by address, where blocks without address are placed last.
|
||||
// - in every scope, most directives and vardecls are moved to the top.
|
||||
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
|
||||
// - the 'start' subroutine is moved to the top.
|
||||
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
|
||||
@ -71,6 +71,69 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
arrayIndexedExpression.indexer.indexNum = expr2
|
||||
arrayIndexedExpression.indexer.origExpression = null
|
||||
return noModifications
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
arrayIndexedExpression.indexer.indexVar = expr2
|
||||
arrayIndexedExpression.indexer.origExpression = null
|
||||
return noModifications
|
||||
}
|
||||
is Expression -> {
|
||||
// replace complex indexing with a temp variable
|
||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||
}
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val subroutine = expr.definingSubroutine()!!
|
||||
val statement = expr.containingStatement()
|
||||
val indexerVarPrefix = "prog8_autovar_index_"
|
||||
val indexerVarName: String
|
||||
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
|
||||
|
||||
val freeVar = repo.filter { statement !in it.value }.map { it.key }.firstOrNull()
|
||||
if(freeVar==null) {
|
||||
// add another loop index var to be used for this expression
|
||||
val statementId = expr.hashCode()
|
||||
indexerVarName = "$indexerVarPrefix$statementId"
|
||||
// create the indexer var at block level scope
|
||||
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
|
||||
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
|
||||
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
|
||||
var statements = repo[indexerVarName]
|
||||
if(statements==null) {
|
||||
statements = mutableSetOf()
|
||||
repo[indexerVarName] = statements
|
||||
}
|
||||
statements.add(statement)
|
||||
} else {
|
||||
// reuse an already created indexer autovar
|
||||
repo.getValue(freeVar).add(statement)
|
||||
indexerVarName = freeVar
|
||||
}
|
||||
|
||||
// assign the indexing expression to the helper variable, and replace the indexer with just the variable
|
||||
val indexerExpression = expr.indexer.origExpression!!
|
||||
val target = AssignTarget(IdentifierReference(listOf(indexerVarName), indexerExpression.position), null, null, indexerExpression.position)
|
||||
val assign = Assignment(target, indexerExpression, indexerExpression.position)
|
||||
val beforeWhat = expr.containingStatement()
|
||||
modifications.add(IAstModification.InsertBefore(beforeWhat, assign, beforeWhat.definingScope()))
|
||||
modifications.add(IAstModification.SetExpression( {
|
||||
expr.indexer.indexVar = it as IdentifierReference
|
||||
expr.indexer.indexNum = null
|
||||
expr.indexer.origExpression = null
|
||||
}, target.identifier!!.copy(), expr.indexer))
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||
@ -81,23 +144,57 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
val declConstValue = declValue.constValue(program)
|
||||
if(declConstValue==null) {
|
||||
// move the vardecl (without value) to the scope and replace this with a regular assignment
|
||||
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
|
||||
if(decl.datatype!=DataType.FLOAT) {
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, declValue, decl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, assign, parent),
|
||||
IAstModification.InsertFirst(decl, decl.definingScope())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val valueType = assignment.value.inferType(program)
|
||||
val targetType = assignment.target.inferType(program, assignment)
|
||||
var assignments = emptyList<Assignment>()
|
||||
|
||||
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
|
||||
val assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
|
||||
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
|
||||
} else {
|
||||
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
|
||||
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
|
||||
}
|
||||
if(assignments.isNotEmpty()) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
|
||||
modifications.add(IAstModification.Remove(assignment, parent))
|
||||
return modifications
|
||||
}
|
||||
|
||||
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
|
||||
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
|
||||
} else {
|
||||
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
|
||||
}
|
||||
}
|
||||
|
||||
if(assignments.isNotEmpty()) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val scope = assignment.definingScope()
|
||||
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
|
||||
modifications.add(IAstModification.Remove(assignment, scope))
|
||||
return modifications
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -150,15 +247,55 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val alv = assign.value as? ArrayLiteralValue
|
||||
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||
}
|
||||
|
||||
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val sourceIdent = assign.value as IdentifierReference
|
||||
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!!
|
||||
if(!sourceVar.isArray) {
|
||||
errors.err("value must be an array", sourceIdent.position)
|
||||
return emptyList()
|
||||
}
|
||||
val alv = sourceVar.value as? ArrayLiteralValue
|
||||
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||
}
|
||||
|
||||
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
|
||||
if(targetVar.arraysize==null) {
|
||||
errors.err("array has no defined size", identifier.position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
|
||||
errors.err("element count mismatch", position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// TODO use a pointer loop instead of individual assignments
|
||||
return alv.value.withIndex().map { (index, value)->
|
||||
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
|
||||
Assignment(AssignTarget(null, idx, null, position), value, value.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val struct = targetVar.struct!!
|
||||
|
||||
val slv = structAssignment.value as? ArrayLiteralValue
|
||||
if(slv==null || slv.value.size != struct.numberOfElements)
|
||||
throw FatalAstException("element count mismatch")
|
||||
if(slv==null || slv.value.size != struct.numberOfElements) {
|
||||
errors.err("element count mismatch", structAssignment.position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
|
||||
targetDecl as VarDecl
|
||||
@ -171,7 +308,8 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
|
||||
// TODO use memcopy beyond a certain number of elements
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
|
@ -23,6 +23,11 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
|
||||
val valueDt = declValue.inferType(program)
|
||||
if(!valueDt.istype(decl.datatype)) {
|
||||
|
||||
// don't add a typecast on an array initializer value
|
||||
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
|
||||
return noModifications
|
||||
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
declValue,
|
||||
TypecastExpression(declValue, decl.datatype, true, declValue.position),
|
||||
@ -124,10 +129,12 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
call as Node)
|
||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||
call as Node)
|
||||
if(arg.second.value is IdentifierReference) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||
call as Node)
|
||||
}
|
||||
} else if(arg.second.value is NumericLiteralValue) {
|
||||
val cast = (arg.second.value as NumericLiteralValue).cast(requiredType)
|
||||
if(cast.isValid)
|
||||
|
@ -12,7 +12,7 @@ internal class VariousCleanups: AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nopStatement, parent))
|
||||
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
|
||||
}
|
||||
|
||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
|
@ -4,10 +4,9 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
@ -43,7 +42,6 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
|
||||
val target = call.target.targetStatement(scope)
|
||||
if (target is Subroutine) {
|
||||
// asmsub types are not checked specifically at this time
|
||||
if(call.args.size != target.parameters.size)
|
||||
return "invalid number of arguments"
|
||||
val paramtypes = target.parameters.map { it.type }
|
||||
@ -53,6 +51,16 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
val expected = paramtypes[mismatch].toString()
|
||||
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
|
||||
}
|
||||
if(target.isAsmSubroutine) {
|
||||
if(target.asmReturnvaluesRegisters.size>1) {
|
||||
// multiple return values will NOT work inside an expression.
|
||||
// they MIGHT work in a regular assignment or just a function call statement.
|
||||
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
|
||||
if(call !is FunctionCallStatement && parent !is Assignment && parent !is VarDecl) {
|
||||
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (target is BuiltinFunctionStatementPlaceholder) {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
|
@ -5,6 +5,7 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
|
||||
|
||||
@ -29,12 +30,6 @@ sealed class Statement : Node {
|
||||
scope.add(name)
|
||||
return scope.joinToString(".")
|
||||
}
|
||||
|
||||
fun definingBlock(): Block {
|
||||
if(this is Block)
|
||||
return this
|
||||
return findParentNode<Block>(this)!!
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -57,6 +52,7 @@ class Block(override val name: String,
|
||||
val isInLibrary: Boolean,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
override lateinit var parent: Node
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -176,6 +172,7 @@ open class VarDecl(val type: VarDeclType,
|
||||
private set
|
||||
var structHasBeenFlattened = false // set later
|
||||
private set
|
||||
var allowInitializeWithZero = true
|
||||
|
||||
// prefix for literal values that are turned into a variable on the heap
|
||||
|
||||
@ -234,7 +231,8 @@ open class VarDecl(val type: VarDeclType,
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===value)
|
||||
// TODO the check that node===value is too strict sometimes, but leaving it out allows for bugs to creep through ... :( Perhaps check when adding the replace if there is already a replace on the same node?
|
||||
require(replacement is Expression)
|
||||
value = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
@ -246,7 +244,12 @@ open class VarDecl(val type: VarDeclType,
|
||||
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
|
||||
}
|
||||
|
||||
fun zeroElementValue() = defaultZero(declaredDatatype, position)
|
||||
fun zeroElementValue(): NumericLiteralValue {
|
||||
if(allowInitializeWithZero)
|
||||
return defaultZero(declaredDatatype, position)
|
||||
else
|
||||
throw CompilerException("attempt to get zero value for vardecl that shouldn't get it")
|
||||
}
|
||||
|
||||
fun flattenStructMembers(): MutableList<Statement> {
|
||||
val result = struct!!.statements.withIndex().map {
|
||||
@ -275,36 +278,72 @@ class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Posit
|
||||
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position)
|
||||
|
||||
|
||||
class ArrayIndex(var index: Expression, override val position: Position) : Node {
|
||||
class ArrayIndex(var origExpression: Expression?, // will be replaced later by either the number or the identifier
|
||||
override val position: Position) : Node {
|
||||
// for code simplicity, either indexed via a constant number or via a variable (no arbitrary expressions)
|
||||
override lateinit var parent: Node
|
||||
var indexNum: NumericLiteralValue? = origExpression as? NumericLiteralValue
|
||||
var indexVar: IdentifierReference? = origExpression as? IdentifierReference
|
||||
|
||||
init {
|
||||
if(indexNum!=null || indexVar!=null)
|
||||
origExpression = null
|
||||
}
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
index.linkParents(this)
|
||||
origExpression?.linkParents(this)
|
||||
indexNum?.linkParents(this)
|
||||
indexVar?.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===index)
|
||||
index = replacement
|
||||
replacement.parent = this
|
||||
require(replacement is Expression)
|
||||
when {
|
||||
node===origExpression -> origExpression = replacement
|
||||
node===indexVar -> {
|
||||
when (replacement) {
|
||||
is NumericLiteralValue -> {
|
||||
indexVar = null
|
||||
indexNum = replacement
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
indexVar = replacement
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("invalid replace")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun forArray(v: ArrayLiteralValue): ArrayIndex {
|
||||
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
|
||||
val indexnum = NumericLiteralValue.optimalNumeric(v.value.size, v.position)
|
||||
return ArrayIndex(indexnum, v.position)
|
||||
}
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) = index.accept(visitor)
|
||||
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
|
||||
|
||||
override fun toString(): String {
|
||||
return("ArrayIndex($index, pos=$position)")
|
||||
fun accept(visitor: IAstVisitor) {
|
||||
origExpression?.accept(visitor)
|
||||
indexNum?.accept(visitor)
|
||||
indexVar?.accept(visitor)
|
||||
}
|
||||
fun accept(visitor: AstWalker, parent: Node) {
|
||||
origExpression?.accept(visitor, this)
|
||||
indexNum?.accept(visitor, this)
|
||||
indexVar?.accept(visitor, this)
|
||||
}
|
||||
|
||||
fun constIndex() = (index as? NumericLiteralValue)?.number?.toInt()
|
||||
override fun toString(): String {
|
||||
return("ArrayIndex($indexNum, $indexVar, pos=$position)")
|
||||
}
|
||||
|
||||
infix fun isSameAs(other: ArrayIndex) = index.isSameAs(other.index)
|
||||
fun constIndex() = indexNum?.number?.toInt()
|
||||
|
||||
infix fun isSameAs(other: ArrayIndex) = indexNum==other.indexNum && indexVar == other.indexVar
|
||||
}
|
||||
|
||||
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
|
||||
@ -405,18 +444,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
companion object {
|
||||
fun fromExpr(expr: Expression): AssignTarget {
|
||||
return when (expr) {
|
||||
is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
|
||||
is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
|
||||
is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
|
||||
else -> throw FatalAstException("invalid expression object $expr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
|
||||
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType { // TODO why does this have the extra 'stmt' scope parameter???
|
||||
if (identifier != null) {
|
||||
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
|
||||
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
|
||||
@ -452,8 +480,8 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
}
|
||||
identifier != null -> value is IdentifierReference && value.nameInSource == identifier!!.nameInSource
|
||||
arrayindexed != null -> {
|
||||
if(value is ArrayIndexedExpression && value.identifier.nameInSource == arrayindexed!!.identifier.nameInSource)
|
||||
arrayindexed!!.arrayspec isSameAs value.arrayspec
|
||||
if(value is ArrayIndexedExpression && value.arrayvar.nameInSource == arrayindexed!!.arrayvar.nameInSource)
|
||||
arrayindexed!!.indexer isSameAs value.indexer
|
||||
else
|
||||
false
|
||||
}
|
||||
@ -472,9 +500,9 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
return addr1 != null && addr2 != null && addr1 == addr2
|
||||
}
|
||||
if (this.arrayindexed != null && other.arrayindexed != null) {
|
||||
if (this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
|
||||
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
|
||||
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
|
||||
if (this.arrayindexed!!.arrayvar.nameInSource == other.arrayindexed!!.arrayvar.nameInSource) {
|
||||
val x1 = this.arrayindexed!!.indexer.constIndex()
|
||||
val x2 = other.arrayindexed!!.indexer.constIndex()
|
||||
return x1 != null && x2 != null && x1 == x2
|
||||
}
|
||||
}
|
||||
@ -499,7 +527,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
}
|
||||
}
|
||||
this.arrayindexed != null -> {
|
||||
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
|
||||
val targetStmt = this.arrayindexed!!.arrayvar.targetVarDecl(namespace)
|
||||
return if (targetStmt?.type == VarDeclType.MEMORY) {
|
||||
val addr = targetStmt.value as? NumericLiteralValue
|
||||
if (addr != null)
|
||||
@ -609,6 +637,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
|
||||
override val position: Position) : INameScope, Statement() {
|
||||
override val name: String
|
||||
override lateinit var parent: Node
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
|
||||
companion object {
|
||||
private var sequenceNumber = 1
|
||||
@ -662,6 +691,7 @@ class Subroutine(override val name: String,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
|
||||
override lateinit var parent: Node
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
val scopedname: String by lazy { makeScopedName(name) }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -894,11 +924,11 @@ class WhenStatement(var condition: Expression,
|
||||
if(choice.values==null)
|
||||
result.add(null to choice)
|
||||
else {
|
||||
val values = choice.values!!.map { it.constValue(program)?.number?.toInt() }
|
||||
if(values.contains(null))
|
||||
result.add(null to choice)
|
||||
else
|
||||
result.add(values.filterNotNull() to choice)
|
||||
val values = choice.values!!.map {
|
||||
val cv = it.constValue(program)
|
||||
cv?.number?.toInt() ?: it.hashCode() // the hashcode is a nonsensical number but it avoids weird AST validation errors later
|
||||
}
|
||||
result.add(values to choice)
|
||||
}
|
||||
}
|
||||
return result
|
||||
@ -920,9 +950,15 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AnonymousScope && node===statements)
|
||||
statements = replacement
|
||||
replacement.parent = this
|
||||
val choiceValues = values
|
||||
if(replacement is AnonymousScope && node===statements) {
|
||||
statements = replacement
|
||||
replacement.parent = this
|
||||
} else if(choiceValues!=null && node in choiceValues) {
|
||||
throw FatalAstException("cannot replace choice values")
|
||||
} else {
|
||||
throw FatalAstException("invalid replacement")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -939,6 +975,7 @@ class StructDecl(override val name: String,
|
||||
override val position: Position): Statement(), INameScope {
|
||||
|
||||
override lateinit var parent: Node
|
||||
override val asmGenInfo = AsmGenInfo()
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
|
@ -19,11 +19,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// a numeric vardecl without an initial value is initialized with zero,
|
||||
// unless there's already an assignment below, that initializes the value
|
||||
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
|
||||
if(nextAssign!=null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
|
||||
decl.value = null
|
||||
else
|
||||
decl.value = decl.zeroElementValue()
|
||||
if(decl.allowInitializeWithZero)
|
||||
{
|
||||
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
|
||||
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
|
||||
decl.value = null
|
||||
else
|
||||
decl.value = decl.zeroElementValue()
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
@ -46,14 +49,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
// use the other part of the expression to split.
|
||||
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent),
|
||||
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
} else {
|
||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent),
|
||||
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
}
|
||||
@ -124,7 +127,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Return
|
||||
&& outerScope !is Block) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
|
||||
}
|
||||
return mods
|
||||
}
|
||||
@ -161,11 +164,13 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type==DataType.UWORD) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||
parent
|
||||
))
|
||||
if(typecast.expression is IdentifierReference) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
|
@ -28,7 +28,9 @@ data class CompilationOptions(val output: OutputType,
|
||||
val zeropage: ZeropageType,
|
||||
val zpReserved: List<IntRange>,
|
||||
val floats: Boolean,
|
||||
val noSysInit: Boolean)
|
||||
val noSysInit: Boolean) {
|
||||
var slowCodegenWarnings = false
|
||||
}
|
||||
|
||||
|
||||
class CompilerException(message: String?) : Exception(message)
|
||||
|
@ -29,6 +29,7 @@ class CompilationResult(val success: Boolean,
|
||||
fun compileProgram(filepath: Path,
|
||||
optimize: Boolean,
|
||||
writeAssembly: Boolean,
|
||||
slowCodegenWarnings: Boolean,
|
||||
compilationTarget: String,
|
||||
outputDir: Path): CompilationResult {
|
||||
var programName = ""
|
||||
@ -49,6 +50,7 @@ fun compileProgram(filepath: Path,
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
|
||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||
programAst = ast
|
||||
importedFiles = imported
|
||||
processAst(programAst, errors, compilationOptions)
|
||||
@ -107,9 +109,9 @@ private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program,
|
||||
// depending on the machine and compiler options we may have to include some libraries
|
||||
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
|
||||
|
||||
// always import prog8lib and math
|
||||
// always import prog8_lib and math
|
||||
importer.importLibraryModule(programAst, "math")
|
||||
importer.importLibraryModule(programAst, "prog8lib")
|
||||
importer.importLibraryModule(programAst, "prog8_lib")
|
||||
errors.handle()
|
||||
return Triple(programAst, compilerOptions, importedFiles)
|
||||
}
|
||||
@ -120,8 +122,6 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
|
||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||
mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" }
|
||||
as? Directive)?.args?.single()?.int ?: 0
|
||||
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
|
||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
|
||||
@ -173,7 +173,8 @@ private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptio
|
||||
errors.handle()
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
programAst.reorderStatements()
|
||||
programAst.reorderStatements(errors)
|
||||
errors.handle()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.variousCleanups()
|
||||
@ -189,10 +190,11 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = programAst.simplifyExpressions()
|
||||
val optsDone2 = programAst.optimizeStatements(errors)
|
||||
val optsDone2 = programAst.splitBinaryExpressions()
|
||||
val optsDone3 = programAst.optimizeStatements(errors)
|
||||
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
errors.handle()
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
if (optsDone1 + optsDone2 + optsDone3 == 0)
|
||||
break
|
||||
}
|
||||
|
||||
@ -208,9 +210,11 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
|
||||
errors.handle()
|
||||
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
|
||||
val callGraph = CallGraph(programAst)
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
errors.handle()
|
||||
programAst.verifyFunctionArgTypes()
|
||||
programAst.moveMainAndStartToFirst()
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
|
||||
|
@ -126,8 +126,10 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||
} else {
|
||||
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
free.addAll(listOf(
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x16, 0x17, 0x18, 0x19, 0x1a,
|
||||
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||
@ -148,16 +150,16 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
))
|
||||
}
|
||||
|
||||
if(options.zeropage!=ZeropageType.DONTUSE) {
|
||||
// add the other free Zp addresses,
|
||||
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
|
||||
// add the free Zp addresses
|
||||
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
|
||||
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
||||
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
|
||||
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
|
||||
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
|
||||
} else {
|
||||
// don't use the zeropage at all
|
||||
free.clear()
|
||||
|
@ -1054,11 +1054,16 @@ object Petscii {
|
||||
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
|
||||
return text.map {
|
||||
val petscii = lookup[it]
|
||||
petscii?.toShort() ?: if(it=='\u0000')
|
||||
0.toShort()
|
||||
else {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
|
||||
petscii?.toShort() ?: when (it) {
|
||||
'\u0000' -> 0.toShort()
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(it.toInt() - 0x8000).toShort()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1072,11 +1077,16 @@ object Petscii {
|
||||
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
|
||||
return text.map{
|
||||
val screencode = lookup[it]
|
||||
screencode?.toShort() ?: if(it=='\u0000')
|
||||
0.toShort()
|
||||
else {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
|
||||
screencode?.toShort() ?: when (it) {
|
||||
'\u0000' -> 0.toShort()
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(it.toInt() - 0x8000).toShort()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,16 +66,21 @@ internal class AsmGen(private val program: Program,
|
||||
block2asm(b)
|
||||
footer()
|
||||
|
||||
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
|
||||
outputFile.printWriter().use {
|
||||
for (line in assemblyLines) { it.println(line) }
|
||||
}
|
||||
|
||||
if(optimize) {
|
||||
assemblyLines.clear()
|
||||
assemblyLines.addAll(outputFile.readLines())
|
||||
var optimizationsDone = 1
|
||||
while (optimizationsDone > 0) {
|
||||
optimizationsDone = optimizeAssembly(assemblyLines)
|
||||
}
|
||||
}
|
||||
|
||||
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
|
||||
outputFile.printWriter().use {
|
||||
for (line in assemblyLines) { it.println(line) }
|
||||
outputFile.printWriter().use {
|
||||
for (line in assemblyLines) { it.println(line) }
|
||||
}
|
||||
}
|
||||
|
||||
return AssemblyProgram(program.name, outputDir)
|
||||
@ -194,11 +199,10 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
|
||||
val variable = IdentifierReference(variableName, decl.position)
|
||||
variable.linkParents(decl.parent)
|
||||
val asmName = asmVariableName(variableName)
|
||||
val asgn = AsmAssignment(
|
||||
AsmAssignSource.fromAstSource(decl.value!!, program),
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, variable = variable),
|
||||
AsmAssignSource.fromAstSource(decl.value!!, program, this),
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, decl.definingSubroutine(), variableAsmName = asmName),
|
||||
false, decl.position)
|
||||
assignmentAsmGen.translateNormalAssignment(asgn)
|
||||
}
|
||||
@ -496,8 +500,14 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun asmSymbolName(name: String) = fixNameSymbols(name)
|
||||
internal fun asmVariableName(name: String) = fixNameSymbols(name)
|
||||
internal fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
internal fun asmVariableName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
|
||||
|
||||
|
||||
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
|
||||
// returns if the pointer is already on the ZP itself or not (in which case SCRATCH_W1 is used as intermediary)
|
||||
// returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary)
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program.namespace)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
@ -538,35 +548,70 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
||||
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
||||
|
||||
|
||||
private val saveRegisterLabels = Stack<String>();
|
||||
|
||||
internal fun saveRegister(register: CpuRegister) {
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
|
||||
else out(" stx _prog8_regsave${register.name}")
|
||||
internal fun saveRegister(register: CpuRegister, dontUseStack: Boolean, scope: Subroutine?) {
|
||||
if(dontUseStack) {
|
||||
when (register) {
|
||||
CpuRegister.A -> {
|
||||
out(" sta _prog8_regsaveA")
|
||||
if (scope != null)
|
||||
scope.asmGenInfo.usedRegsaveA = true
|
||||
}
|
||||
CpuRegister.X -> {
|
||||
out(" stx _prog8_regsaveX")
|
||||
if (scope != null)
|
||||
scope.asmGenInfo.usedRegsaveX = true
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
out(" sty _prog8_regsaveY")
|
||||
if (scope != null)
|
||||
scope.asmGenInfo.usedRegsaveY = true
|
||||
}
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
|
||||
else out(" sty _prog8_regsave${register.name}")
|
||||
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
|
||||
else {
|
||||
out(" stx _prog8_regsaveX")
|
||||
if (scope != null)
|
||||
scope.asmGenInfo.usedRegsaveX = true
|
||||
}
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
|
||||
else {
|
||||
out(" sty _prog8_regsaveY")
|
||||
if (scope != null)
|
||||
scope.asmGenInfo.usedRegsaveY = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreRegister(register: CpuRegister) {
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" pla")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
|
||||
else out(" ldx _prog8_regsave${register.name}")
|
||||
internal fun restoreRegister(register: CpuRegister, dontUseStack: Boolean) {
|
||||
if(dontUseStack) {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" sta _prog8_regsaveA")
|
||||
CpuRegister.X -> out(" ldx _prog8_regsaveX")
|
||||
CpuRegister.Y -> out(" ldy _prog8_regsaveY")
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
|
||||
else out(" ldy _prog8_regsave${register.name}")
|
||||
|
||||
} else {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pla")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
|
||||
else out(" ldx _prog8_regsaveX")
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
|
||||
else out(" ldy _prog8_regsaveY")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -588,9 +633,10 @@ internal class AsmGen(private val program: Program,
|
||||
if (builtinFunc != null) {
|
||||
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
|
||||
} else {
|
||||
functioncallAsmGen.translateFunctionCall(stmt)
|
||||
// discard any results from the stack:
|
||||
val sub = stmt.target.targetSubroutine(program.namespace)!!
|
||||
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
|
||||
functioncallAsmGen.translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
|
||||
// discard any results from the stack:
|
||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||
for ((t, reg) in returns) {
|
||||
if (reg.stack) {
|
||||
@ -598,6 +644,8 @@ internal class AsmGen(private val program: Program,
|
||||
else if (t == DataType.FLOAT) out(" inx | inx | inx")
|
||||
}
|
||||
}
|
||||
if(preserveStatusRegisterAfterCall)
|
||||
out(" plp\t; restore status flags from call")
|
||||
}
|
||||
}
|
||||
is Assignment -> assignmentAsmGen.translate(stmt)
|
||||
@ -628,131 +676,75 @@ internal class AsmGen(private val program: Program,
|
||||
register: CpuRegister,
|
||||
addOneExtra: Boolean=false) {
|
||||
val reg = register.toString().toLowerCase()
|
||||
val index = expr.arrayspec.index
|
||||
if(index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize() + if(addOneExtra) 1 else 0
|
||||
val indexnum = expr.indexer.constIndex()
|
||||
if(indexnum!=null) {
|
||||
val indexValue = indexnum * elementDt.memorySize() + if(addOneExtra) 1 else 0
|
||||
out(" ld$reg #$indexValue")
|
||||
return
|
||||
}
|
||||
|
||||
val indexName = asmVariableName(expr.indexer.indexVar!!)
|
||||
if(addOneExtra) {
|
||||
// add 1 to the result
|
||||
if (index is IdentifierReference) {
|
||||
val indexName = asmVariableName(index)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
out(" ldy $indexName | iny")
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" tya")
|
||||
CpuRegister.X -> out(" tyx")
|
||||
CpuRegister.Y -> {}
|
||||
}
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
out(" ldy $indexName | iny")
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" tya")
|
||||
CpuRegister.X -> out(" tyx")
|
||||
CpuRegister.Y -> {}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | sec | rol a")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
sec
|
||||
adc $indexName""")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
out("""
|
||||
inc P8ESTACK_LO,x
|
||||
bne +
|
||||
inc P8ESTACK_HI,x
|
||||
+""")
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
|
||||
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
|
||||
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | sec | rol a")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
sec
|
||||
adc $indexName""")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
} else {
|
||||
if (index is IdentifierReference) {
|
||||
val indexName = asmVariableName(index)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | asl a")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $indexName""")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
|
||||
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
|
||||
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
asl a""")
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ESTACK_LO,x""")
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | asl a")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $indexName""")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -760,11 +752,14 @@ internal class AsmGen(private val program: Program,
|
||||
internal fun translateExpression(expression: Expression) =
|
||||
expressionsAsmGen.translateExpression(expression)
|
||||
|
||||
internal fun translateExpression(indexer: ArrayIndex) =
|
||||
expressionsAsmGen.translateExpression(indexer)
|
||||
|
||||
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
|
||||
|
||||
internal fun translateFunctionCall(functionCall: FunctionCall) =
|
||||
functioncallAsmGen.translateFunctionCall(functionCall)
|
||||
internal fun translateFunctionCall(functionCall: FunctionCall, preserveStatusRegisterAfterCall: Boolean) =
|
||||
functioncallAsmGen.translateFunctionCall(functionCall, preserveStatusRegisterAfterCall)
|
||||
|
||||
internal fun translateNormalAssignment(assign: AsmAssignment) =
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
@ -806,10 +801,13 @@ internal class AsmGen(private val program: Program,
|
||||
out("; statements")
|
||||
sub.statements.forEach{ translate(it) }
|
||||
out("; variables")
|
||||
out("""
|
||||
; register saves
|
||||
_prog8_regsaveX .byte 0
|
||||
_prog8_regsaveY .byte 0""") // TODO only generate these bytes if they're actually used by saveRegister()
|
||||
out("; register saves")
|
||||
if(sub.asmGenInfo.usedRegsaveA)
|
||||
out("_prog8_regsaveA .byte 0")
|
||||
if(sub.asmGenInfo.usedRegsaveX)
|
||||
out("_prog8_regsaveX .byte 0")
|
||||
if(sub.asmGenInfo.usedRegsaveY)
|
||||
out("_prog8_regsaveY .byte 0")
|
||||
vardecls2asm(sub.statements)
|
||||
out(" .pend\n")
|
||||
}
|
||||
@ -934,6 +932,8 @@ _prog8_regsaveY .byte 0""") // TODO only generate these bytes if the
|
||||
}
|
||||
|
||||
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
if(constIterations==0)
|
||||
return
|
||||
// note: A/Y must have been loaded with the number of iterations already!
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
out("""
|
||||
@ -963,8 +963,12 @@ $counterVar .word 0""")
|
||||
}
|
||||
|
||||
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
if(constIterations==0)
|
||||
return
|
||||
// note: A must have been loaded with the number of iterations already!
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
if(constIterations==null)
|
||||
out(" beq $endLabel")
|
||||
out("""
|
||||
sta $counterVar
|
||||
$repeatLabel""")
|
||||
@ -1169,4 +1173,40 @@ $counterVar .byte 0""")
|
||||
val assembly = asm.assembly.trimEnd().trimStart('\n')
|
||||
assemblyLines.add(assembly)
|
||||
}
|
||||
|
||||
internal fun signExtendStackLsb(valueDt: DataType) {
|
||||
// sign extend signed byte on stack to signed word
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta P8ESTACK_HI+1,x""")
|
||||
}
|
||||
else -> throw AssemblyError("need byte type")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun signExtendVariableLsb(asmvar: String, valueDt: DataType) {
|
||||
// sign extend signed byte in a word variable
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
out(" lda #0 | sta $asmvar+1")
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
out("""
|
||||
lda $asmvar+1
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta $asmvar+1""")
|
||||
}
|
||||
else -> throw AssemblyError("need byte type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -196,8 +196,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
|
||||
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
|
||||
(first.startsWith("stx ") && second.startsWith("ldx "))
|
||||
) {
|
||||
val firstLoc = first.substring(4)
|
||||
val secondLoc = second.substring(4)
|
||||
val firstLoc = first.substring(4).trimStart()
|
||||
val secondLoc = second.substring(4).trimStart()
|
||||
if (firstLoc == secondLoc) {
|
||||
mods.add(Modification(pair[1].index, true, null))
|
||||
}
|
||||
|
@ -159,8 +159,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
asmgen.out(" jsr prog8_lib.ror2_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
@ -182,8 +182,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
asmgen.out(" jsr prog8_lib.ror2_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -204,8 +204,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
asmgen.out(" jsr prog8_lib.ror_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
@ -234,8 +234,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
asmgen.out(" jsr prog8_lib.ror_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -256,8 +256,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
asmgen.out(" jsr prog8_lib.rol2_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
@ -279,8 +279,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
asmgen.out(" jsr prog8_lib.rol2_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -301,8 +301,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
asmgen.out(" jsr prog8_lib.rol_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
@ -331,8 +331,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.translateExpression(what.arrayvar)
|
||||
asmgen.translateExpression(what.indexer)
|
||||
asmgen.out(" jsr prog8_lib.rol_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
@ -390,12 +390,22 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
private fun funcStrlen(fcall: IFunctionCall) {
|
||||
val name = asmgen.asmVariableName(fcall.args[0] as IdentifierReference)
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr prog8_lib.strlen
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
val type = fcall.args[0].inferType(program)
|
||||
when {
|
||||
type.istype(DataType.STR) -> asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr prog8_lib.strlen
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
type.istype(DataType.UWORD) -> asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr prog8_lib.strlen
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
else -> throw AssemblyError("strlen requires str or uword arg")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSwap(fcall: IFunctionCall) {
|
||||
@ -468,19 +478,26 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
if(first is ArrayIndexedExpression && second is ArrayIndexedExpression) {
|
||||
val indexValue1 = first.arrayspec.index as? NumericLiteralValue
|
||||
val indexName1 = first.arrayspec.index as? IdentifierReference
|
||||
val indexValue2 = second.arrayspec.index as? NumericLiteralValue
|
||||
val indexName2 = second.arrayspec.index as? IdentifierReference
|
||||
val arrayVarName1 = asmgen.asmVariableName(first.identifier)
|
||||
val arrayVarName2 = asmgen.asmVariableName(second.identifier)
|
||||
val arrayVarName1 = asmgen.asmVariableName(first.arrayvar)
|
||||
val arrayVarName2 = asmgen.asmVariableName(second.arrayvar)
|
||||
val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
|
||||
if(indexValue1!=null && indexValue2!=null) {
|
||||
swapArrayValues(elementDt, arrayVarName1, indexValue1, arrayVarName2, indexValue2)
|
||||
val firstNum = first.indexer.indexNum
|
||||
val firstVar = first.indexer.indexVar
|
||||
val secondNum = second.indexer.indexNum
|
||||
val secondVar = second.indexer.indexVar
|
||||
|
||||
if(firstNum!=null && secondNum!=null) {
|
||||
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondNum)
|
||||
return
|
||||
} else if(indexName1!=null && indexName2!=null) {
|
||||
swapArrayValues(elementDt, arrayVarName1, indexName1, arrayVarName2, indexName2)
|
||||
} else if(firstVar!=null && secondVar!=null) {
|
||||
swapArrayValues(elementDt, arrayVarName1, firstVar, arrayVarName2, secondVar)
|
||||
return
|
||||
} else if(firstNum!=null && secondVar!=null) {
|
||||
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondVar)
|
||||
return
|
||||
} else if(firstVar!=null && secondNum!=null) {
|
||||
swapArrayValues(elementDt, arrayVarName1, firstVar, arrayVarName2, secondNum)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -488,9 +505,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
// all other types of swap() calls are done via the evaluation stack
|
||||
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
|
||||
return when (expr) {
|
||||
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, variable=expr)
|
||||
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, array = expr)
|
||||
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, memory = DirectMemoryWrite(expr.addressExpression, expr.position))
|
||||
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, expr.definingSubroutine(), variableAsmName = asmgen.asmVariableName(expr))
|
||||
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, expr.definingSubroutine(), array = expr)
|
||||
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, expr.definingSubroutine(), memory = DirectMemoryWrite(expr.addressExpression, expr.position))
|
||||
else -> throw AssemblyError("invalid expression object $expr")
|
||||
}
|
||||
}
|
||||
@ -499,12 +516,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.translateExpression(second)
|
||||
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val assignFirst = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, datatype),
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, datatype),
|
||||
targetFromExpr(first, datatype),
|
||||
false, first.position
|
||||
)
|
||||
val assignSecond = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, datatype),
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, datatype),
|
||||
targetFromExpr(second, datatype),
|
||||
false, second.position
|
||||
)
|
||||
@ -626,6 +643,122 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) {
|
||||
val index1 = indexValue1.number.toInt() * elementDt.memorySize()
|
||||
val idxAsmName2 = asmgen.asmVariableName(indexName2)
|
||||
when(elementDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out("""
|
||||
lda $arrayVarName1 + $index1
|
||||
pha
|
||||
ldy $idxAsmName2
|
||||
lda $arrayVarName2,y
|
||||
sta $arrayVarName1 + $index1
|
||||
pla
|
||||
sta $arrayVarName2,y
|
||||
""")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out("""
|
||||
lda $arrayVarName1 + $index1
|
||||
pha
|
||||
lda $idxAsmName2
|
||||
asl a
|
||||
tay
|
||||
lda $arrayVarName2,y
|
||||
sta $arrayVarName1 + $index1
|
||||
pla
|
||||
sta $arrayVarName2,y
|
||||
lda $arrayVarName1 + $index1+1
|
||||
pha
|
||||
lda $arrayVarName2+1,y
|
||||
sta $arrayVarName1 + $index1+1
|
||||
pla
|
||||
sta $arrayVarName2+1,y
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<(${arrayVarName1}+$index1)
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>(${arrayVarName1}+$index1)
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #>$arrayVarName1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda $idxAsmName2
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $idxAsmName2
|
||||
adc #<$arrayVarName1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ jsr floats.swap_floats
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid aray elt type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) {
|
||||
val idxAsmName1 = asmgen.asmVariableName(indexName1)
|
||||
val index2 = indexValue2.number.toInt() * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out("""
|
||||
lda $arrayVarName2 + $index2
|
||||
pha
|
||||
ldy $idxAsmName1
|
||||
lda $arrayVarName1,y
|
||||
sta $arrayVarName2 + $index2
|
||||
pla
|
||||
sta $arrayVarName1,y
|
||||
""")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out("""
|
||||
lda $arrayVarName2 + $index2
|
||||
pha
|
||||
lda $idxAsmName1
|
||||
asl a
|
||||
tay
|
||||
lda $arrayVarName1,y
|
||||
sta $arrayVarName2 + $index2
|
||||
pla
|
||||
sta $arrayVarName1,y
|
||||
lda $arrayVarName2 + $index2+1
|
||||
pha
|
||||
lda $arrayVarName1+1,y
|
||||
sta $arrayVarName2 + $index2+1
|
||||
pla
|
||||
sta $arrayVarName1+1,y
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #>$arrayVarName1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda $idxAsmName1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $idxAsmName1
|
||||
adc #<$arrayVarName1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ lda #<(${arrayVarName2}+$index2)
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>(${arrayVarName2}+$index2)
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr floats.swap_floats
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid aray elt type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
|
||||
translateFunctionArguments(fcall.args, func)
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
|
@ -3,6 +3,7 @@ package prog8.compiler.target.c64.codegen
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.ArrayIndex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.CpuType
|
||||
@ -22,7 +23,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
is DirectMemoryRead -> translateDirectMemReadExpression(expression, true)
|
||||
is NumericLiteralValue -> translateExpression(expression)
|
||||
is IdentifierReference -> translateExpression(expression)
|
||||
is FunctionCall -> translateExpression(expression)
|
||||
is FunctionCall -> translateFunctionCallResultOntoStack(expression)
|
||||
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
||||
}
|
||||
@ -90,6 +91,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
in ByteDatatypes -> translateByteEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
|
||||
in WordDatatypes -> translateWordEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
|
||||
DataType.FLOAT -> translateFloatEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
|
||||
DataType.STR -> translateStringEquals(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
|
||||
else -> throw AssemblyError("weird operand datatype")
|
||||
}
|
||||
}
|
||||
@ -131,6 +133,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
in ByteDatatypes -> translateByteNotEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
|
||||
in WordDatatypes -> translateWordNotEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
|
||||
DataType.FLOAT -> translateFloatNotEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
|
||||
DataType.STR -> translateStringNotEquals(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
|
||||
else -> throw AssemblyError("weird operand datatype")
|
||||
}
|
||||
}
|
||||
@ -145,6 +148,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
translateExpression(right)
|
||||
asmgen.out(" jsr floats.less_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
|
||||
}
|
||||
DataType.STR -> translateStringLess(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
|
||||
else -> throw AssemblyError("weird operand datatype")
|
||||
}
|
||||
}
|
||||
@ -159,6 +163,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
translateExpression(right)
|
||||
asmgen.out(" jsr floats.lesseq_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
|
||||
}
|
||||
DataType.STR -> translateStringLessOrEqual(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
|
||||
else -> throw AssemblyError("weird operand datatype")
|
||||
}
|
||||
}
|
||||
@ -173,6 +178,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
translateExpression(right)
|
||||
asmgen.out(" jsr floats.greater_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
|
||||
}
|
||||
DataType.STR -> translateStringGreater(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
|
||||
else -> throw AssemblyError("weird operand datatype")
|
||||
}
|
||||
}
|
||||
@ -187,6 +193,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
translateExpression(right)
|
||||
asmgen.out(" jsr floats.greatereq_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
|
||||
}
|
||||
DataType.STR -> translateStringGreaterOrEqual(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
|
||||
else -> throw AssemblyError("weird operand datatype")
|
||||
}
|
||||
}
|
||||
@ -942,14 +949,106 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
asmgen.out(" jsr floats.notequal_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
|
||||
}
|
||||
|
||||
private fun translateExpression(expression: FunctionCall) {
|
||||
private fun translateStringEquals(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
|
||||
val leftNam = asmgen.asmVariableName(left)
|
||||
val rightNam = asmgen.asmVariableName(right)
|
||||
asmgen.out("""
|
||||
lda #<$rightNam
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$rightNam
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda #<$leftNam
|
||||
ldy #>$leftNam
|
||||
jsr prog8_lib.strcmp_mem
|
||||
cmp #0
|
||||
bne $jumpIfFalseLabel""")
|
||||
}
|
||||
|
||||
private fun translateStringNotEquals(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
|
||||
val leftNam = asmgen.asmVariableName(left)
|
||||
val rightNam = asmgen.asmVariableName(right)
|
||||
asmgen.out("""
|
||||
lda #<$rightNam
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$rightNam
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda #<$leftNam
|
||||
ldy #>$leftNam
|
||||
jsr prog8_lib.strcmp_mem
|
||||
cmp #0
|
||||
beq $jumpIfFalseLabel""")
|
||||
}
|
||||
|
||||
private fun translateStringLess(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
|
||||
val leftNam = asmgen.asmVariableName(left)
|
||||
val rightNam = asmgen.asmVariableName(right)
|
||||
asmgen.out("""
|
||||
lda #<$rightNam
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$rightNam
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda #<$leftNam
|
||||
ldy #>$leftNam
|
||||
jsr prog8_lib.strcmp_mem
|
||||
bpl $jumpIfFalseLabel""")
|
||||
}
|
||||
|
||||
private fun translateStringGreater(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
|
||||
val leftNam = asmgen.asmVariableName(left)
|
||||
val rightNam = asmgen.asmVariableName(right)
|
||||
asmgen.out("""
|
||||
lda #<$rightNam
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$rightNam
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda #<$leftNam
|
||||
ldy #>$leftNam
|
||||
jsr prog8_lib.strcmp_mem
|
||||
beq $jumpIfFalseLabel
|
||||
bmi $jumpIfFalseLabel""")
|
||||
}
|
||||
|
||||
private fun translateStringLessOrEqual(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
|
||||
val leftNam = asmgen.asmVariableName(left)
|
||||
val rightNam = asmgen.asmVariableName(right)
|
||||
asmgen.out("""
|
||||
lda #<$rightNam
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$rightNam
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda #<$leftNam
|
||||
ldy #>$leftNam
|
||||
jsr prog8_lib.strcmp_mem
|
||||
beq +
|
||||
bpl $jumpIfFalseLabel
|
||||
+""")
|
||||
}
|
||||
|
||||
private fun translateStringGreaterOrEqual(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
|
||||
val leftNam = asmgen.asmVariableName(left)
|
||||
val rightNam = asmgen.asmVariableName(right)
|
||||
asmgen.out("""
|
||||
lda #<$rightNam
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$rightNam
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda #<$leftNam
|
||||
ldy #>$leftNam
|
||||
jsr prog8_lib.strcmp_mem
|
||||
beq +
|
||||
bmi $jumpIfFalseLabel
|
||||
+""")
|
||||
}
|
||||
|
||||
private fun translateFunctionCallResultOntoStack(expression: FunctionCall) {
|
||||
val functionName = expression.target.nameInSource.last()
|
||||
val builtinFunc = BuiltinFunctions[functionName]
|
||||
if (builtinFunc != null) {
|
||||
asmgen.translateFunctioncallExpression(expression, builtinFunc)
|
||||
} else {
|
||||
val sub = expression.target.targetSubroutine(program.namespace)!!
|
||||
asmgen.translateFunctionCall(expression)
|
||||
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
|
||||
asmgen.translateFunctionCall(expression, preserveStatusRegisterAfterCall)
|
||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||
for ((_, reg) in returns) {
|
||||
if (!reg.stack) {
|
||||
@ -992,11 +1091,11 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: TypecastExpression) {
|
||||
translateExpression(expr.expression)
|
||||
when(expr.expression.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||
private fun translateExpression(typecast: TypecastExpression) {
|
||||
translateExpression(typecast.expression)
|
||||
when(typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE -> {
|
||||
when(expr.type) {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
|
||||
@ -1010,24 +1109,16 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
when(expr.type) {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
// sign extend
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta P8ESTACK_HI+1,x""")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> asmgen.signExtendStackLsb(DataType.BYTE)
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_b2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when(expr.type) {
|
||||
when(typecast.type) {
|
||||
DataType.BYTE, DataType.UBYTE -> {}
|
||||
DataType.WORD, DataType.UWORD -> {}
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_uw2float")
|
||||
@ -1036,7 +1127,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
when(expr.type) {
|
||||
when(typecast.type) {
|
||||
DataType.BYTE, DataType.UBYTE -> {}
|
||||
DataType.WORD, DataType.UWORD -> {}
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.stack_w2float")
|
||||
@ -1045,7 +1136,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when(expr.type) {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr floats.stack_float2uw")
|
||||
DataType.BYTE -> asmgen.out(" jsr floats.stack_float2w")
|
||||
DataType.UWORD -> asmgen.out(" jsr floats.stack_float2uw")
|
||||
@ -1055,6 +1146,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.STR -> {
|
||||
if (typecast.type != DataType.UWORD && typecast.type == DataType.STR)
|
||||
throw AssemblyError("cannot typecast a string into another incompatitble type")
|
||||
}
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
@ -1128,6 +1223,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: BinaryExpression) {
|
||||
// TODO needs to use optimized assembly generation like the assignment instructions. But avoid code duplication.... rewrite all expressions into assignment form?
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
@ -1137,6 +1233,106 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
|
||||
// see if we can apply some optimized routines
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val leftVal = expr.left.constValue(program)?.number?.toInt()
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if (leftVal!=null && leftVal in -4..4) {
|
||||
translateExpression(expr.right)
|
||||
if(rightDt in ByteDatatypes) {
|
||||
val incdec = if(leftVal<0) "dec" else "inc"
|
||||
repeat(leftVal.absoluteValue) {
|
||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
// word
|
||||
if(leftVal<0) {
|
||||
repeat(leftVal.absoluteValue) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
dec P8ESTACK_HI+1,x
|
||||
+ dec P8ESTACK_LO+1,x""")
|
||||
}
|
||||
} else {
|
||||
repeat(leftVal) {
|
||||
asmgen.out("""
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
else if (rightVal!=null && rightVal in -4..4)
|
||||
{
|
||||
translateExpression(expr.left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
val incdec = if(rightVal<0) "dec" else "inc"
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
// word
|
||||
if(rightVal<0) {
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
dec P8ESTACK_HI+1,x
|
||||
+ dec P8ESTACK_LO+1,x""")
|
||||
}
|
||||
} else {
|
||||
repeat(rightVal) {
|
||||
asmgen.out("""
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if (rightVal!=null && rightVal in -4..4)
|
||||
{
|
||||
translateExpression(expr.left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
val incdec = if(rightVal<0) "inc" else "dec"
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out(" $incdec P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
// word
|
||||
if(rightVal>0) {
|
||||
repeat(rightVal.absoluteValue) {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
dec P8ESTACK_HI+1,x
|
||||
+ dec P8ESTACK_LO+1,x""")
|
||||
}
|
||||
} else {
|
||||
repeat(rightVal) {
|
||||
asmgen.out("""
|
||||
inc P8ESTACK_LO+1,x
|
||||
bne +
|
||||
inc P8ESTACK_HI+1,x
|
||||
+""")
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
translateExpression(expr.left)
|
||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||
@ -1218,6 +1414,16 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
if(value!=null) {
|
||||
if(rightDt in IntegerDatatypes) {
|
||||
val amount = value.number.toInt()
|
||||
if(amount==2) {
|
||||
// optimize x*2 common case
|
||||
translateExpression(expr.left)
|
||||
if(leftDt in ByteDatatypes) {
|
||||
asmgen.out(" asl P8ESTACK_LO+1,x")
|
||||
} else {
|
||||
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
|
||||
}
|
||||
return
|
||||
}
|
||||
when(rightDt) {
|
||||
DataType.UBYTE -> {
|
||||
if(amount in asmgen.optimizedByteMultiplications) {
|
||||
@ -1262,20 +1468,41 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
}
|
||||
"/" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
if(rightVal!=null && rightVal==2) {
|
||||
translateExpression(expr.left)
|
||||
when(leftDt) {
|
||||
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
|
||||
DataType.BYTE -> asmgen.out(" asl P8ESTACK_LO+1,x | ror P8ESTACK_LO+1,x")
|
||||
DataType.UWORD -> asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||
DataType.WORD -> asmgen.out(" asl P8ESTACK_HI+1,x | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
|
||||
else -> throw AssemblyError("wrong dt")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the general, non-optimized cases
|
||||
translateExpression(expr.left)
|
||||
translateExpression(expr.right)
|
||||
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
||||
|
||||
when (leftDt) {
|
||||
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
|
||||
else -> throw AssemblyError("non-numerical datatype")
|
||||
// the general, non-optimized cases TODO optimize more cases....
|
||||
translateExpression(expr.left)
|
||||
translateExpression(expr.right)
|
||||
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in comparisonOperators) {
|
||||
translateCompareStrings(expr.operator)
|
||||
}
|
||||
else {
|
||||
when (leftDt) {
|
||||
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
|
||||
else -> throw AssemblyError("non-numerical datatype")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1316,11 +1543,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
|
||||
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
|
||||
val index = arrayExpr.arrayspec.index
|
||||
val elementDt = arrayExpr.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val arrayVarName = asmgen.asmVariableName(arrayExpr.identifier)
|
||||
if(index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
|
||||
if(arrayExpr.indexer.indexNum!=null) {
|
||||
val indexValue = arrayExpr.indexer.constIndex()!! * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
|
||||
@ -1359,6 +1585,14 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
|
||||
fun translateExpression(indexer: ArrayIndex) {
|
||||
// it is either a number, or a variable
|
||||
val indexNum = indexer.indexNum
|
||||
val indexVar = indexer.indexVar
|
||||
indexNum?.let { asmgen.translateExpression(indexNum) }
|
||||
indexVar?.let { asmgen.translateExpression(indexVar) }
|
||||
}
|
||||
|
||||
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
|
||||
when(operator) {
|
||||
"**" -> throw AssemblyError("** operator requires floats")
|
||||
@ -1447,4 +1681,36 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
else -> throw AssemblyError("invalid operator $operator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateCompareStrings(operator: String) {
|
||||
asmgen.out(" jsr prog8_lib.func_strcmp | lda P8ESTACK_LO+1,x") // result of compare in A
|
||||
when(operator) {
|
||||
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO+1,x")
|
||||
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO+1,x")
|
||||
"<=" -> asmgen.out("""
|
||||
bpl +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta P8ESTACK_LO+1,x""")
|
||||
">=" -> asmgen.out("""
|
||||
bmi +
|
||||
lda #1
|
||||
bne ++
|
||||
+ lda #0
|
||||
+ sta P8ESTACK_LO+1,x""")
|
||||
"<" -> asmgen.out("""
|
||||
bmi +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO+1,x""")
|
||||
">" -> asmgen.out("""
|
||||
bpl +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta P8ESTACK_LO+1,x""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -610,8 +610,8 @@ $endLabel""")
|
||||
}
|
||||
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) {
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), variable=stmt.loopVar)
|
||||
val src = AsmAssignSource.fromAstSource(range.from, program).adjustDataTypeToTarget(target)
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine(), variableAsmName=asmgen.asmVariableName(stmt.loopVar))
|
||||
val src = AsmAssignSource.fromAstSource(range.from, program, asmgen).adjustSignedUnsigned(target)
|
||||
val assign = AsmAssignment(src, target, false, range.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
@ -13,13 +14,13 @@ import prog8.compiler.target.c64.codegen.assignment.*
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall) {
|
||||
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
|
||||
// output the code to setup the parameters and perform the actual call
|
||||
// does NOT output the code to deal with the result values!
|
||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
|
||||
if(saveX)
|
||||
asmgen.saveRegister(CpuRegister.X)
|
||||
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine())
|
||||
|
||||
val subName = asmgen.asmSymbolName(stmt.target)
|
||||
if(stmt.args.isNotEmpty()) {
|
||||
@ -57,8 +58,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
asmgen.out(" jsr $subName")
|
||||
|
||||
if(preserveStatusRegisterAfterCall) {
|
||||
asmgen.out(" php\t; save status flags from call")
|
||||
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
|
||||
// must take care of popping this value again at the end!
|
||||
}
|
||||
|
||||
if(saveX)
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
|
||||
}
|
||||
|
||||
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||
@ -148,11 +155,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val scopedParamVar = (sub.scopedname+"."+parameter.value.name).split(".")
|
||||
val identifier = IdentifierReference(scopedParamVar, sub.position)
|
||||
identifier.linkParents(value.parent)
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, variable = identifier)
|
||||
val source = AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(tgt)
|
||||
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, sub, variableAsmName = varName)
|
||||
val source = AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(tgt)
|
||||
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
|
||||
asmgen.translateNormalAssignment(asgn)
|
||||
}
|
||||
@ -170,13 +175,22 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val statusflag = paramRegister.statusflag
|
||||
val register = paramRegister.registerOrPair
|
||||
val stack = paramRegister.stack
|
||||
val requiredDt = parameter.value.type
|
||||
if(requiredDt!=valueDt) {
|
||||
if(valueDt largerThan requiredDt)
|
||||
throw AssemblyError("can only convert byte values to word param types")
|
||||
}
|
||||
when {
|
||||
stack -> {
|
||||
// push arg onto the stack
|
||||
// note: argument order is reversed (first argument will be deepest on the stack)
|
||||
asmgen.translateExpression(value)
|
||||
if(requiredDt!=valueDt)
|
||||
asmgen.signExtendStackLsb(valueDt)
|
||||
}
|
||||
statusflag!=null -> {
|
||||
if(requiredDt!=valueDt)
|
||||
throw AssemblyError("for statusflag, byte value is required")
|
||||
if (statusflag == Statusflag.Pc) {
|
||||
// this param needs to be set last, right before the jsr
|
||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||
@ -216,15 +230,30 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
else -> {
|
||||
// via register or register pair
|
||||
val target = AsmAssignTarget.fromRegisters(register!!, program, asmgen)
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
val addr = AddressOf(value as IdentifierReference, Position.DUMMY)
|
||||
AsmAssignSource.fromAstSource(addr, program).adjustDataTypeToTarget(target)
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(target)
|
||||
val target = AsmAssignTarget.fromRegisters(register!!, sub, program, asmgen)
|
||||
if(requiredDt largerThan valueDt) {
|
||||
// we need to sign extend the source, do this via temporary word variable
|
||||
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
|
||||
val scratchTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UBYTE, sub, scratchVar)
|
||||
val source = AsmAssignSource.fromAstSource(value, program, asmgen)
|
||||
asmgen.translateNormalAssignment(AsmAssignment(source, scratchTarget, false, value.position))
|
||||
asmgen.signExtendVariableLsb(scratchVar, valueDt)
|
||||
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, DataType.UWORD, scratchVar)
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||
}
|
||||
else {
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
if(value is IdentifierReference) {
|
||||
val addr = AddressOf(value, Position.DUMMY)
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||
}
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||
}
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||
}
|
||||
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
val targetIdent = stmt.target.identifier
|
||||
val targetMemory = stmt.target.memoryAddress
|
||||
val targetArrayIdx = stmt.target.arrayindexed
|
||||
val scope = stmt.definingSubroutine()
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val what = asmgen.asmVariableName(targetIdent)
|
||||
@ -69,67 +70,64 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.identifier)
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
||||
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when(index) {
|
||||
is NumericLiteralValue -> {
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname+$indexValue
|
||||
bne +
|
||||
dec $asmArrayvarname+$indexValue+1
|
||||
if(targetArrayIdx.indexer.indexNum!=null) {
|
||||
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname+$indexValue
|
||||
bne +
|
||||
dec $asmArrayvarname+$indexValue+1
|
||||
+ dec $asmArrayvarname+$indexValue
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||
asmgen.saveRegister(CpuRegister.X)
|
||||
asmgen.out(" tax")
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname,x
|
||||
bne +
|
||||
dec $asmArrayvarname+1,x
|
||||
+ dec $asmArrayvarname
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
ldy #>$asmArrayvarname
|
||||
clc
|
||||
adc #<$asmArrayvarname
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.inc_var_f""")
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||
}
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||
asmgen.saveRegister(CpuRegister.X, false, scope)
|
||||
asmgen.out(" tax")
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname,x
|
||||
bne +
|
||||
dec $asmArrayvarname+1,x
|
||||
+ dec $asmArrayvarname
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
ldy #>$asmArrayvarname
|
||||
clc
|
||||
adc #<$asmArrayvarname
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.inc_var_f""")
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
asmgen.restoreRegister(CpuRegister.X, false)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird target type ${stmt.target}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
|
||||
@ -29,10 +30,11 @@ internal enum class SourceStorageKind {
|
||||
}
|
||||
|
||||
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
program: Program,
|
||||
asmgen: AsmGen,
|
||||
private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
val datatype: DataType,
|
||||
val variable: IdentifierReference? = null,
|
||||
val scope: Subroutine?,
|
||||
private val variableAsmName: String? = null,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryWrite? = null,
|
||||
val register: RegisterOrPair? = null,
|
||||
@ -40,20 +42,16 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
)
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
|
||||
val vardecl by lazy { variable!!.targetVarDecl(program.namespace)!! }
|
||||
val asmVarname by lazy {
|
||||
if(variable!=null)
|
||||
asmgen.asmVariableName(variable)
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
val asmVarname: String
|
||||
get() = if(array==null)
|
||||
variableAsmName!!
|
||||
else
|
||||
asmgen.asmVariableName(array!!.identifier)
|
||||
}
|
||||
asmgen.asmVariableName(array.arrayvar)
|
||||
|
||||
lateinit var origAssign: AsmAssignment
|
||||
|
||||
init {
|
||||
if(variable!=null && vardecl.type == VarDeclType.CONST)
|
||||
throw AssemblyError("can't assign to a constant")
|
||||
if(register!=null && datatype !in IntegerDatatypes)
|
||||
throw AssemblyError("register must be integer type")
|
||||
}
|
||||
@ -62,29 +60,30 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
|
||||
val dt = inferType(program, assign).typeOrElse(DataType.STRUCT)
|
||||
when {
|
||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, variable=identifier, origAstTarget = this)
|
||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, memory = memoryAddress, origAstTarget = this)
|
||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine(), memory = memoryAddress, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
|
||||
fun fromRegisters(registers: RegisterOrPair, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
||||
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
||||
when(registers) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, register = registers)
|
||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, register = registers)
|
||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
val datatype: DataType,
|
||||
val variable: IdentifierReference? = null,
|
||||
private val variableAsmName: String? = null,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryRead? = null,
|
||||
val register: CpuRegister? = null,
|
||||
@ -93,53 +92,73 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
)
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
|
||||
val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! }
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
|
||||
|
||||
val asmVarname: String
|
||||
get() = if(array==null)
|
||||
variableAsmName!!
|
||||
else
|
||||
asmgen.asmVariableName(array.arrayvar)
|
||||
|
||||
companion object {
|
||||
fun fromAstSource(value: Expression, program: Program): AsmAssignSource {
|
||||
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||
val cv = value.constValue(program)
|
||||
if(cv!=null)
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, cv.type, number = cv)
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
||||
|
||||
return when(value) {
|
||||
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, value.type, number = cv)
|
||||
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, value.type, number = cv)
|
||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is IdentifierReference -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, dt, variable = value)
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = asmgen.asmVariableName(value))
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
AsmAssignSource(SourceStorageKind.MEMORY, program, DataType.UBYTE, memory = value)
|
||||
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, dt, array = value)
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||
}
|
||||
else -> {
|
||||
if(value is FunctionCall) {
|
||||
// functioncall.
|
||||
val asmSub = value.target.targetStatement(program.namespace)
|
||||
if(asmSub is Subroutine && asmSub.isAsmSubroutine) {
|
||||
when (asmSub.asmReturnvaluesRegisters.count { rr -> rr.registerOrPair!=null }) {
|
||||
0 -> throw AssemblyError("can't translate zero return values in assignment")
|
||||
1 -> {
|
||||
// assignment generation itself must make sure the status register is correct after the subroutine call, if status register is involved!
|
||||
val reg = asmSub.asmReturnvaluesRegisters.single { rr->rr.registerOrPair!=null }.registerOrPair!!
|
||||
val dt = when(reg) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> DataType.UBYTE
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY -> DataType.UWORD
|
||||
}
|
||||
return AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
|
||||
}
|
||||
else -> throw AssemblyError("can't translate multiple return values in assignment")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, dt, expression = value)
|
||||
return AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAstValue(): Expression = when(kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> number!!
|
||||
SourceStorageKind.VARIABLE -> variable!!
|
||||
SourceStorageKind.ARRAY -> array!!
|
||||
SourceStorageKind.MEMORY -> memory!!
|
||||
SourceStorageKind.EXPRESSION -> expression!!
|
||||
SourceStorageKind.REGISTER -> throw AssemblyError("cannot get a register source as Ast node")
|
||||
SourceStorageKind.STACK -> throw AssemblyError("cannot get a stack source as Ast node")
|
||||
}
|
||||
|
||||
fun withAdjustedDt(newType: DataType) =
|
||||
AsmAssignSource(kind, program, newType, variable, array, memory, register, number, expression)
|
||||
|
||||
fun adjustDataTypeToTarget(target: AsmAssignTarget): AsmAssignSource {
|
||||
fun adjustSignedUnsigned(target: AsmAssignTarget): AsmAssignSource {
|
||||
// allow some signed/unsigned relaxations
|
||||
|
||||
fun withAdjustedDt(newType: DataType) =
|
||||
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
|
||||
|
||||
if(target.datatype!=datatype) {
|
||||
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
|
||||
return withAdjustedDt(target.datatype)
|
||||
@ -160,6 +179,9 @@ internal class AsmAssignment(val source: AsmAssignSource,
|
||||
|
||||
init {
|
||||
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||
require(source.datatype.memorySize() == target.datatype.memorySize()) { "source and target datatype must be same storage class" }
|
||||
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
|
||||
require(source.datatype.memorySize() <= target.datatype.memorySize()) {
|
||||
"source storage size must be less or equal to target datatype storage size"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
|
||||
fun translate(assignment: Assignment) {
|
||||
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
|
||||
val source = AsmAssignSource.fromAstSource(assignment.value, program).adjustDataTypeToTarget(target)
|
||||
val source = AsmAssignSource.fromAstSource(assignment.value, program, asmgen).adjustSignedUnsigned(target)
|
||||
|
||||
val assign = AsmAssignment(source, target, assignment.isAugmentable, assignment.position)
|
||||
target.origAssign = assign
|
||||
@ -34,7 +34,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
// simple case: assign a constant number
|
||||
val num = assign.source.number!!.number
|
||||
when (assign.source.datatype) {
|
||||
when (assign.target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toShort())
|
||||
DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt())
|
||||
DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
|
||||
@ -43,23 +43,22 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
// simple case: assign from another variable
|
||||
val variable = assign.source.variable!!
|
||||
when (assign.source.datatype) {
|
||||
val variable = assign.source.asmVarname
|
||||
when (assign.target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
|
||||
DataType.UWORD, DataType.WORD -> assignVariableWord(assign.target, variable)
|
||||
DataType.FLOAT -> assignVariableFloat(assign.target, variable)
|
||||
in PassByReferenceDatatypes -> assignAddressOf(assign.target, variable)
|
||||
DataType.STR -> assignVariableString(assign.target, variable)
|
||||
else -> throw AssemblyError("unsupported assignment target type ${assign.target.datatype}")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.ARRAY -> {
|
||||
val value = assign.source.array!!
|
||||
val elementDt = assign.source.datatype
|
||||
val index = value.arrayspec.index
|
||||
val arrayVarName = asmgen.asmVariableName(value.identifier)
|
||||
if (index is NumericLiteralValue) {
|
||||
val arrayVarName = asmgen.asmVariableName(value.arrayvar)
|
||||
if (value.indexer.indexNum!=null) {
|
||||
// constant array index value
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||
val indexValue = value.indexer.constIndex()!! * elementDt.memorySize()
|
||||
when (elementDt) {
|
||||
in ByteDatatypes ->
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
|
||||
@ -114,31 +113,46 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
val value = assign.source.expression!!
|
||||
when(value) {
|
||||
is AddressOf -> assignAddressOf(assign.target, value.identifier)
|
||||
when(val value = assign.source.expression!!) {
|
||||
is AddressOf -> {
|
||||
val sourceName = value.identifier.firstStructVarName(program.namespace) ?: asmgen.asmVariableName(value.identifier)
|
||||
assignAddressOf(assign.target, sourceName)
|
||||
}
|
||||
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
|
||||
is IdentifierReference -> throw AssemblyError("source kind should have been variable")
|
||||
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
|
||||
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
|
||||
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, assign)
|
||||
// is FunctionCall -> {
|
||||
// if (assign.target.kind == TargetStorageKind.STACK) {
|
||||
// asmgen.translateExpression(value)
|
||||
// assignStackValue(assign.target)
|
||||
// } else {
|
||||
// val functionName = value.target.nameInSource.last()
|
||||
// val builtinFunc = BuiltinFunctions[functionName]
|
||||
// if (builtinFunc != null) {
|
||||
// println("!!!!BUILTIN-FUNCCALL target=${assign.target.kind} $value") // TODO optimize certain functions?
|
||||
// }
|
||||
// asmgen.translateExpression(value)
|
||||
// assignStackValue(assign.target)
|
||||
// }
|
||||
// }
|
||||
is FunctionCall -> {
|
||||
if(value.target.targetSubroutine(program.namespace)?.isAsmSubroutine==true) {
|
||||
// handle asmsub functioncalls specifically, without shoving stuff on the estack
|
||||
val sub = value.target.targetSubroutine(program.namespace)!!
|
||||
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any { it.statusflag != null }
|
||||
asmgen.translateFunctionCall(value, preserveStatusRegisterAfterCall)
|
||||
when((sub.asmReturnvaluesRegisters.single { it.registerOrPair!=null }).registerOrPair) {
|
||||
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
|
||||
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
|
||||
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
|
||||
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
|
||||
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
|
||||
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
|
||||
else -> throw AssemblyError("should be just one register byte result value")
|
||||
}
|
||||
if(preserveStatusRegisterAfterCall)
|
||||
asmgen.out(" plp\t; restore status flags from call")
|
||||
} else {
|
||||
// regular subroutine, return values are (for now) always done via the stack... TODO optimize this
|
||||
asmgen.translateExpression(value)
|
||||
if(assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
||||
asmgen.signExtendStackLsb(assign.source.datatype)
|
||||
assignStackValue(assign.target)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// everything else just evaluate via the stack.
|
||||
asmgen.translateExpression(value)
|
||||
if(assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
|
||||
asmgen.signExtendStackLsb(assign.source.datatype)
|
||||
assignStackValue(assign.target)
|
||||
}
|
||||
}
|
||||
@ -156,9 +170,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val valueDt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when(value) {
|
||||
is IdentifierReference -> {
|
||||
if (valueDt == DataType.UBYTE || valueDt == DataType.BYTE) {
|
||||
if(targetDt in WordDatatypes) {
|
||||
assignVariableByteIntoWord(target, value, valueDt)
|
||||
if(targetDt in WordDatatypes) {
|
||||
if(valueDt==DataType.UBYTE) {
|
||||
assignVariableUByteIntoWord(target, value)
|
||||
return
|
||||
}
|
||||
if(valueDt==DataType.BYTE) {
|
||||
assignVariableByteIntoWord(target, value)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -189,7 +207,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
is FunctionCall -> {}
|
||||
else -> {
|
||||
// TODO optimize the others further?
|
||||
println("warning: slow stack evaluation used for typecast: into $targetDt at ${value.position}")
|
||||
if(this.asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used for typecast: into $targetDt at ${value.position}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,6 +240,18 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
jsr floats.pop_float
|
||||
""")
|
||||
}
|
||||
DataType.STR -> {
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
inx
|
||||
lda P8ESTACK_HI,x
|
||||
tay
|
||||
lda P8ESTACK_LO,x
|
||||
jsr prog8_lib.strcpy""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
}
|
||||
}
|
||||
@ -229,66 +260,60 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
storeByteViaRegisterAInMemoryAddress("P8ESTACK_LO,x", target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = target.array!!.arrayspec.index
|
||||
when {
|
||||
target.constArrayIndexValue!=null -> {
|
||||
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta ${target.asmVarname}+$scaledIdx
|
||||
lda P8ESTACK_HI,x
|
||||
sta ${target.asmVarname}+$scaledIdx+1
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
jsr floats.pop_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
if(target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
}
|
||||
index is IdentifierReference -> {
|
||||
when(target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname},y")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta ${target.asmVarname},y
|
||||
lda P8ESTACK_HI,x
|
||||
sta ${target.asmVarname}+1,y
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>${target.asmVarname}
|
||||
clc
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.pop_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
in WordDatatypes -> {
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta ${target.asmVarname}+$scaledIdx
|
||||
lda P8ESTACK_HI,x
|
||||
sta ${target.asmVarname}+$scaledIdx+1
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
jsr floats.pop_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
|
||||
}
|
||||
else
|
||||
{
|
||||
target.array!!
|
||||
when(target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname},y")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta ${target.asmVarname},y
|
||||
lda P8ESTACK_HI,x
|
||||
sta ${target.asmVarname}+1,y
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>${target.asmVarname}
|
||||
clc
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.pop_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -319,9 +344,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignAddressOf(target: AsmAssignTarget, name: IdentifierReference) {
|
||||
val sourceName = name.firstStructVarName(program.namespace) ?: asmgen.fixNameSymbols(name.nameInSource.joinToString("."))
|
||||
|
||||
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
@ -346,19 +369,54 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
val srcname = asmgen.asmVariableName(name)
|
||||
asmgen.out("""
|
||||
lda #<$srcname
|
||||
lda #<$sourceName
|
||||
sta P8ESTACK_LO,x
|
||||
lda #>$srcname
|
||||
lda #>$sourceName
|
||||
sta P8ESTACK_HI,x
|
||||
dex""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableWord(target: AsmAssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(variable)
|
||||
private fun assignVariableString(target: AsmAssignTarget, sourceName: String) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
when(target.datatype) {
|
||||
DataType.UWORD -> {
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
sta ${target.asmVarname}
|
||||
lda #>$sourceName
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
jsr prog8_lib.strcpy""")
|
||||
}
|
||||
else -> throw AssemblyError("assign string to incompatible variable type")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
sta P8ESTACK_LO,x
|
||||
lda #>$sourceName+1
|
||||
sta P8ESTACK_HI,x
|
||||
dex""")
|
||||
}
|
||||
else -> throw AssemblyError("string-assign to weird target")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableWord(target: AsmAssignTarget, sourceName: String) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
@ -372,73 +430,66 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
throw AssemblyError("no asm gen for assign wordvar $sourceName to memory ${target.memory}")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = target.array!!.arrayspec.index
|
||||
when {
|
||||
target.constArrayIndexValue!=null -> {
|
||||
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${target.asmVarname}+$scaledIdx
|
||||
lda $sourceName+1
|
||||
sta ${target.asmVarname}+$scaledIdx+1
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
jsr floats.copy_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
target.array!!
|
||||
if(target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${target.asmVarname}+$scaledIdx
|
||||
lda $sourceName+1
|
||||
sta ${target.asmVarname}+$scaledIdx+1
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
jsr floats.copy_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
}
|
||||
index is IdentifierReference -> {
|
||||
when(target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${target.asmVarname},y
|
||||
lda $sourceName+1
|
||||
sta ${target.asmVarname}+1,y
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #<$sourceName
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy #>$sourceName
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #>${target.asmVarname}
|
||||
clc
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
}
|
||||
else
|
||||
{
|
||||
when(target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${target.asmVarname},y
|
||||
lda $sourceName+1
|
||||
sta ${target.asmVarname}+1,y
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #<$sourceName
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy #>$sourceName
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #>${target.asmVarname}
|
||||
clc
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ jsr floats.copy_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | lda $sourceName+1 | sta P8ESTACK_HI,x | dex")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -461,8 +512,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableFloat(target: AsmAssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(variable)
|
||||
private fun assignVariableFloat(target: AsmAssignTarget, sourceName: String) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
@ -479,16 +529,23 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// TODO optimize this, but the situation doesn't occur very often
|
||||
// if(target.constArrayIndexValue!=null) {
|
||||
// TODO("const index ${target.constArrayIndexValue}")
|
||||
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||
// }
|
||||
val index = target.array!!.arrayspec.index
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr floats.push_float")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname} | jsr floats.pop_float_to_indexed_var")
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1""")
|
||||
if(target.array!!.indexer.indexNum!=null) {
|
||||
val index = target.array.indexer.constIndex()!!
|
||||
asmgen.out(" lda #$index")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
||||
asmgen.out(" lda $asmvarname")
|
||||
}
|
||||
asmgen.out(" jsr floats.set_array_float")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
|
||||
@ -496,8 +553,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableByte(target: AsmAssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(variable)
|
||||
private fun assignVariableByte(target: AsmAssignTarget, sourceName: String) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
@ -509,22 +565,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = target.array!!.arrayspec.index
|
||||
when {
|
||||
target.constArrayIndexValue!=null -> {
|
||||
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
index is IdentifierReference -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
|
||||
}
|
||||
if (target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
else {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, target.datatype, CpuRegister.Y)
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
@ -546,10 +593,69 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableByteIntoWord(wordtarget: AsmAssignTarget, bytevar: IdentifierReference, valueDt: DataType) {
|
||||
if(valueDt == DataType.BYTE)
|
||||
TODO("sign extend byte to word")
|
||||
private fun assignVariableByteIntoWord(wordtarget: AsmAssignTarget, bytevar: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(bytevar)
|
||||
when (wordtarget.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${wordtarget.asmVarname}
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta ${wordtarget.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// TODO optimize slow stack evaluation for this case, see assignVariableUByteIntoWord
|
||||
if(this.asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used for sign-extend byte typecast at ${bytevar.position}")
|
||||
asmgen.translateExpression(wordtarget.origAssign.source.expression!!)
|
||||
assignStackValue(wordtarget)
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(wordtarget.register!!) {
|
||||
RegisterOrPair.AX -> asmgen.out("""
|
||||
lda $sourceName
|
||||
pha
|
||||
ora #$7f
|
||||
bmi +
|
||||
ldx #0
|
||||
+ tax
|
||||
pla""")
|
||||
RegisterOrPair.AY -> asmgen.out("""
|
||||
lda $sourceName
|
||||
pha
|
||||
ora #$7f
|
||||
bmi +
|
||||
ldy #0
|
||||
+ tay
|
||||
pla""")
|
||||
RegisterOrPair.XY -> asmgen.out("""
|
||||
lda $sourceName
|
||||
tax
|
||||
ora #$7f
|
||||
bmi +
|
||||
ldy #0
|
||||
+ tay""")
|
||||
else -> throw AssemblyError("only reg pairs are words")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta P8ESTACK_LO,x
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta P8ESTACK_HI,x
|
||||
dex""")
|
||||
}
|
||||
else -> throw AssemblyError("target type isn't word")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableUByteIntoWord(wordtarget: AsmAssignTarget, bytevar: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(bytevar)
|
||||
when(wordtarget.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -561,22 +667,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = wordtarget.array!!.arrayspec.index
|
||||
when {
|
||||
wordtarget.constArrayIndexValue!=null -> {
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
||||
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx | lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||
}
|
||||
index is IdentifierReference -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array, wordtarget.datatype, CpuRegister.Y)
|
||||
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname},y | lda #0 | iny | sta ${wordtarget.asmVarname},y")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | lda #0 | sta P8ESTACK_HI,x | dex")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||
popAndWriteArrayvalueWithUnscaledIndexA(wordtarget.datatype, wordtarget.asmVarname)
|
||||
}
|
||||
if (wordtarget.constArrayIndexValue!=null) {
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
||||
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx | lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||
}
|
||||
else {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array!!, wordtarget.datatype, CpuRegister.Y)
|
||||
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname},y | lda #0 | iny | sta ${wordtarget.asmVarname},y")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
@ -589,13 +686,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.out("""
|
||||
lda #$sourceName
|
||||
lda $sourceName
|
||||
sta P8ESTACK_LO,x
|
||||
lda #0
|
||||
sta P8ESTACK_HI,x
|
||||
dex""")
|
||||
}
|
||||
else -> throw AssemblyError("other types aren't word")
|
||||
else -> throw AssemblyError("target type isn't word")
|
||||
}
|
||||
}
|
||||
|
||||
@ -609,40 +706,21 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
storeRegisterInMemoryAddress(register, target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = target.array!!.arrayspec.index
|
||||
when (index) {
|
||||
is NumericLiteralValue -> {
|
||||
val memindex = index.number.toInt()
|
||||
when {
|
||||
target.constArrayIndexValue!=null -> {
|
||||
when (register) {
|
||||
CpuRegister.A -> asmgen.out(" sta ${target.asmVarname}+$memindex")
|
||||
CpuRegister.X -> asmgen.out(" stx ${target.asmVarname}+$memindex")
|
||||
CpuRegister.Y -> asmgen.out(" sty ${target.asmVarname}+$memindex")
|
||||
CpuRegister.A -> asmgen.out(" sta ${target.asmVarname}+${target.constArrayIndexValue}")
|
||||
CpuRegister.X -> asmgen.out(" stx ${target.asmVarname}+${target.constArrayIndexValue}")
|
||||
CpuRegister.Y -> asmgen.out(" sty ${target.asmVarname}+${target.constArrayIndexValue}")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
else -> {
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> asmgen.out(" txa")
|
||||
CpuRegister.Y -> asmgen.out(" tya")
|
||||
}
|
||||
asmgen.out(" ldy ${asmgen.asmVariableName(index)} | sta ${target.asmVarname},y")
|
||||
}
|
||||
else -> {
|
||||
asmgen.saveRegister(register)
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.restoreRegister(register)
|
||||
when (register) {
|
||||
CpuRegister.A -> asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
CpuRegister.X -> asmgen.out(" stx P8ZP_SCRATCH_B1")
|
||||
CpuRegister.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1")
|
||||
}
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
tay
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta ${target.asmVarname},y
|
||||
""")
|
||||
asmgen.out(" ldy ${asmgen.asmVariableName(target.array!!.indexer.indexVar!!)} | sta ${target.asmVarname},y")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -684,6 +762,54 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
|
||||
require(target.datatype in WordDatatypes)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname} | stx ${target.asmVarname}+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname} | sty ${target.asmVarname}+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" stx ${target.asmVarname} | sty ${target.asmVarname}+1")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
TODO("store register pair $regs into word-array ${target.array}")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AX -> when(target.register!!) {
|
||||
RegisterOrPair.AY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG") }
|
||||
RegisterOrPair.AX -> { }
|
||||
RegisterOrPair.XY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG | tax") }
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
RegisterOrPair.AY -> when(target.register!!) {
|
||||
RegisterOrPair.AY -> { }
|
||||
RegisterOrPair.AX -> { asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
|
||||
RegisterOrPair.XY -> { asmgen.out(" tax") }
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
RegisterOrPair.XY -> when(target.register!!) {
|
||||
RegisterOrPair.AY -> { asmgen.out(" txa") }
|
||||
RegisterOrPair.AX -> { asmgen.out(" txa | sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
|
||||
RegisterOrPair.XY -> { }
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
when(regs) {
|
||||
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex")
|
||||
RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here")
|
||||
else -> throw AssemblyError("expected reg pair")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't store word into memory byte")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -707,19 +833,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// TODO optimize this, but the situation doesn't occur very often
|
||||
// if(target.constArrayIndexValue!=null) {
|
||||
// TODO("const index ${target.constArrayIndexValue}")
|
||||
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||
// }
|
||||
val index = target.array!!.arrayspec.index
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
asl a
|
||||
tay
|
||||
lda #<${word.toHex()}
|
||||
sta ${target.asmVarname},y
|
||||
lda #>${word.toHex()}
|
||||
@ -754,25 +869,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
storeByteViaRegisterAInMemoryAddress("#${byte.toHex()}", target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = target.array!!.arrayspec.index
|
||||
when {
|
||||
target.constArrayIndexValue!=null -> {
|
||||
val indexValue = target.constArrayIndexValue!!
|
||||
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname}+$indexValue")
|
||||
}
|
||||
index is IdentifierReference -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
|
||||
asmgen.out(" lda #<${byte.toHex()} | sta ${target.asmVarname},y")
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
lda #${byte.toHex()}
|
||||
sta ${target.asmVarname},y
|
||||
""")
|
||||
}
|
||||
if (target.constArrayIndexValue!=null) {
|
||||
val indexValue = target.constArrayIndexValue!!
|
||||
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname}+$indexValue")
|
||||
}
|
||||
else {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UBYTE, CpuRegister.Y)
|
||||
asmgen.out(" lda #<${byte.toHex()} | sta ${target.asmVarname},y")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> when(target.register!!) {
|
||||
@ -816,15 +919,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// TODO optimize this, but the situation doesn't occur very often
|
||||
// if(target.constArrayIndexValue!=null) {
|
||||
// TODO("const index ${target.constArrayIndexValue}")
|
||||
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||
// }
|
||||
val index = target.array!!.arrayspec.index
|
||||
if (index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * DataType.FLOAT.memorySize()
|
||||
if (target.array!!.indexer.indexNum!=null) {
|
||||
val indexValue = target.array.indexer.constIndex()!! * DataType.FLOAT.memorySize()
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta ${target.asmVarname}+$indexValue
|
||||
@ -834,12 +930,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sta ${target.asmVarname}+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
asmgen.translateExpression(index)
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda $asmvarname
|
||||
jsr floats.set_0_array_float
|
||||
""")
|
||||
}
|
||||
@ -870,16 +967,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// TODO optimize this, but the situation doesn't occur very often
|
||||
// if(target.constArrayIndexValue!=null) {
|
||||
// TODO("const index ${target.constArrayIndexValue}")
|
||||
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||
// }
|
||||
val index = target.array!!.arrayspec.index
|
||||
val arrayVarName = target.asmVarname
|
||||
if (index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * DataType.FLOAT.memorySize()
|
||||
if (target.array!!.indexer.indexNum!=null) {
|
||||
val indexValue = target.array.indexer.constIndex()!! * DataType.FLOAT.memorySize()
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta $arrayVarName+$indexValue
|
||||
@ -893,7 +983,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sta $arrayVarName+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
asmgen.translateExpression(index)
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
||||
asmgen.out("""
|
||||
lda #<${constFloat}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
@ -903,6 +993,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>${arrayVarName}
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda $asmvarname
|
||||
jsr floats.set_array_float
|
||||
""")
|
||||
}
|
||||
@ -1047,6 +1138,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.storeByteIntoPointer(addressExpr, ldaInstructionArg)
|
||||
}
|
||||
else -> {
|
||||
asmgen.out(" lda $ldaInstructionArg | pha")
|
||||
asmgen.translateExpression(addressExpr)
|
||||
asmgen.out("""
|
||||
inx
|
||||
@ -1054,8 +1146,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda P8ESTACK_HI,x
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda $ldaInstructionArg
|
||||
ldy #0
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
}
|
||||
}
|
||||
@ -1079,9 +1171,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.storeByteIntoPointer(addressExpr, null)
|
||||
}
|
||||
else -> {
|
||||
asmgen.saveRegister(register)
|
||||
asmgen.saveRegister(register, false, memoryAddress.definingSubroutine())
|
||||
asmgen.translateExpression(addressExpr)
|
||||
asmgen.restoreRegister(CpuRegister.A)
|
||||
asmgen.restoreRegister(CpuRegister.A, false)
|
||||
asmgen.out("""
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
@ -1093,24 +1185,4 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun popAndWriteArrayvalueWithUnscaledIndexA(elementDt: DataType, asmArrayvarname: String) {
|
||||
when (elementDt) {
|
||||
in ByteDatatypes ->
|
||||
asmgen.out(" tay | inx | lda P8ESTACK_LO,x | sta $asmArrayvarname,y")
|
||||
in WordDatatypes ->
|
||||
asmgen.out(" asl a | tay | inx | lda P8ESTACK_LO,x | sta $asmArrayvarname,y | lda P8ESTACK_HI,x | sta $asmArrayvarname+1,y")
|
||||
DataType.FLOAT ->
|
||||
// scaling * 5 is done in the subroutine that's called
|
||||
asmgen.out("""
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
lda #<$asmArrayvarname
|
||||
ldy #>$asmArrayvarname
|
||||
jsr floats.pop_float_to_indexed_var
|
||||
""")
|
||||
else ->
|
||||
throw AssemblyError("weird array type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,13 +3,13 @@ package prog8.compiler.target.c64.codegen.assignment
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
|
||||
import prog8.compiler.toHex
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private val assignmentAsmGen: AssignmentAsmGen,
|
||||
@ -19,8 +19,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
require(assign.isAugmentable)
|
||||
require(assign.source.kind== SourceStorageKind.EXPRESSION)
|
||||
|
||||
val value = assign.source.expression!!
|
||||
when (value) {
|
||||
when (val value = assign.source.expression!!) {
|
||||
is PrefixExpression -> {
|
||||
// A = -A , A = +A, A = ~A, A = not A
|
||||
val type = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
@ -137,13 +136,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when {
|
||||
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble())
|
||||
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident)
|
||||
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble(), target.scope)
|
||||
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident, target.scope)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
inplaceModification_float_value_to_variable(target.asmVarname, operator, value)
|
||||
inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope)
|
||||
}
|
||||
else -> inplaceModification_float_value_to_variable(target.asmVarname, operator, value)
|
||||
else -> inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
@ -180,7 +179,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
println("warning: slow stack evaluation used (1): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO optimize...
|
||||
if(asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used (1): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO optimize...
|
||||
asmgen.translateExpression(memory.addressExpression)
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
||||
val zp = CompilationTarget.instance.machine.zeropage
|
||||
@ -199,7 +199,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
println("*** TODO optimize simple inplace array assignment ${target.array} $operator= $value")
|
||||
if(asmgen.options.slowCodegenWarnings)
|
||||
println("*** TODO optimize simple inplace array assignment ${target.array} $operator= $value")
|
||||
assignmentAsmGen.translateNormalAssignment(target.origAssign) // TODO get rid of this fallback for the most common cases here
|
||||
}
|
||||
TargetStorageKind.REGISTER -> TODO("reg in-place modification")
|
||||
@ -220,125 +221,95 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_value_to_memory(pointervar: IdentifierReference, operator: String, value: Expression) {
|
||||
println("warning: slow stack evaluation used (3): @(${pointervar.nameInSource.last()}) $operator= ${value::class.simpleName} at ${value.position}") // TODO
|
||||
if(asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used (3): @(${pointervar.nameInSource.last()}) $operator= ${value::class.simpleName} at ${value.position}") // TODO
|
||||
asmgen.translateExpression(value)
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" clc | adc P8ESTACK_LO+1,x")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
"+" -> asmgen.out(" clc | adc P8ESTACK_LO+1,x")
|
||||
"-" -> asmgen.out(" sec | sbc P8ESTACK_LO+1,x")
|
||||
"*" -> asmgen.out(" pha | lda P8ESTACK_LO+1,x | tay | pla | jsr math.multiply_bytes | ldy #0")
|
||||
"/" -> asmgen.out(" pha | lda P8ESTACK_LO+1,x | tay | pla | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||
"%" -> asmgen.out(" pha | lda P8ESTACK_LO+1,x | tay | pla | jsr math.divmod_ub_asm | ldy #0")
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
pla
|
||||
rts
|
||||
+ tay
|
||||
pla
|
||||
- asl a
|
||||
dey
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
"-" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sec | sbc P8ESTACK_LO+1,x")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
"*" -> {
|
||||
TODO("mul mem byte")// asmgen.out(" jsr prog8_lib.mul_byte")
|
||||
}
|
||||
"/" -> TODO("div mem byte")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
||||
"%" -> {
|
||||
TODO("mem byte remainder")
|
||||
// if(types==DataType.BYTE)
|
||||
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
// asmgen.out(" jsr prog8_lib.remainder_ub")
|
||||
}
|
||||
"<<" -> TODO("mem ubyte asl")
|
||||
">>" -> TODO("mem ubyte lsr")
|
||||
"&" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" and P8ESTACK_LO+1,x")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
"^" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" eor P8ESTACK_LO+1,x")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
"|" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" ora P8ESTACK_LO+1,x")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
">>" -> {
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda P8ESTACK_LO+1,x
|
||||
bne +
|
||||
pla
|
||||
rts
|
||||
+ tay
|
||||
pla
|
||||
- lsr a
|
||||
dey
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
"&" -> asmgen.out(" and P8ESTACK_LO+1,x")
|
||||
"^" -> asmgen.out(" eor P8ESTACK_LO+1,x")
|
||||
"|" -> asmgen.out(" ora P8ESTACK_LO+1,x")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
asmgen.out(" inx")
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_variable_to_memory(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
|
||||
val otherName = asmgen.asmVariableName(value)
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" clc | adc $otherName")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
"+" -> asmgen.out(" clc | adc $otherName")
|
||||
"-" -> asmgen.out(" sec | sbc $otherName")
|
||||
"*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes | ldy #0")
|
||||
"/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||
"%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | ldy #0")
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- asl a
|
||||
dey
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
"-" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sec | sbc $otherName")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
"*" -> {
|
||||
TODO("mem mul")// asmgen.out(" jsr prog8_lib.mul_byte")
|
||||
}
|
||||
"/" -> TODO("mem div")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
||||
"%" -> {
|
||||
TODO("mem byte remainder")
|
||||
// if(types==DataType.BYTE)
|
||||
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
// asmgen.out(" jsr prog8_lib.remainder_ub")
|
||||
}
|
||||
"<<" -> TODO("mem ubyte asl")
|
||||
">>" -> TODO("mem ubyte lsr")
|
||||
"&" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" and $otherName")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
"^" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" eor $otherName")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
"|" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" ora $otherName")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
">>" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- lsr a
|
||||
dey
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
"&" -> asmgen.out(" and $otherName")
|
||||
"^" -> asmgen.out(" eor $otherName")
|
||||
"|" -> asmgen.out(" ora $otherName")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_litval_to_memory(pointervar: IdentifierReference, operator: String, value: Int) {
|
||||
@ -361,26 +332,35 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
"*" -> {
|
||||
if(value in asmgen.optimizedByteMultiplications) {
|
||||
TODO("optimized mem mul ubyte litval $value")
|
||||
} else {
|
||||
TODO("mem mul ubyte litval $value")
|
||||
// asmgen.out(" jsr prog8_lib.mul_byte")
|
||||
}
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" jsr math.mul_byte_${value}")
|
||||
else
|
||||
asmgen.out(" ldy #$value | jsr math.multiply_bytes | ldy #0")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
"/" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
TODO("mem div byte litval")
|
||||
// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya | ldy #0")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
"%" -> {
|
||||
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
if(value==0)
|
||||
throw AssemblyError("division by zero")
|
||||
TODO("mem byte remainder litval")
|
||||
// if(types==DataType.BYTE)
|
||||
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
// asmgen.out(" jsr prog8_lib.remainder_ub")
|
||||
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | ldy #0")
|
||||
if(ptrOnZp)
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
"<<" -> {
|
||||
if (value > 0) {
|
||||
@ -433,25 +413,24 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private fun inplaceModification_byte_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) {
|
||||
// this should be the last resort for code generation for this,
|
||||
// because the value is evaluated onto the eval stack (=slow).
|
||||
println("warning: slow stack evaluation used (5): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
|
||||
if(asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used (5): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
|
||||
asmgen.translateExpression(value)
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> asmgen.out(" lda $name | clc | adc P8ESTACK_LO+1,x | sta $name")
|
||||
"-" -> asmgen.out(" lda $name | sec | sbc P8ESTACK_LO+1,x | sta $name")
|
||||
"*" -> {
|
||||
TODO("var mul byte expr")
|
||||
// check optimizedByteMultiplications
|
||||
// asmgen.out(" jsr prog8_lib.mul_byte")
|
||||
}
|
||||
"*" -> asmgen.out(" lda P8ESTACK_LO+1,x | ldy $name | jsr math.multiply_bytes | sta $name")
|
||||
"/" -> {
|
||||
TODO("var div byte expr")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
|
||||
if(dt==DataType.UBYTE)
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x | tay | lda $name | jsr math.divmod_ub_asm | sty $name")
|
||||
else
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x | tay | lda $name | jsr math.divmod_b_asm | sty $name")
|
||||
}
|
||||
"%" -> {
|
||||
TODO("var byte remainder expr")
|
||||
// if(types==DataType.BYTE)
|
||||
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
// asmgen.out(" jsr prog8_lib.remainder_ub")
|
||||
if(dt==DataType.BYTE)
|
||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x | tay | lda $name | jsr math.divmod_ub_asm | sta $name")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.translateExpression(value)
|
||||
@ -519,25 +498,31 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- asl $name
|
||||
dey
|
||||
bne -""")
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
">>" -> {
|
||||
if(dt==DataType.UBYTE) {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- lsr $name
|
||||
dey
|
||||
bne -""")
|
||||
bne -
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- lda $name
|
||||
asl a
|
||||
ror $name
|
||||
dey
|
||||
bne -""")
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
}
|
||||
"&" -> asmgen.out(" lda $name | and $otherName | sta $name")
|
||||
@ -571,33 +556,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"+" -> asmgen.out(" lda $name | clc | adc #$value | sta $name")
|
||||
"-" -> asmgen.out(" lda $name | sec | sbc #$value | sta $name")
|
||||
"*" -> {
|
||||
if(dt == DataType.UBYTE) {
|
||||
if(value in asmgen.optimizedByteMultiplications) {
|
||||
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
|
||||
} else {
|
||||
TODO("var mul ubyte litval $value")
|
||||
// asmgen.out(" jsr prog8_lib.mul_byte")
|
||||
}
|
||||
} else {
|
||||
if(value.absoluteValue in asmgen.optimizedByteMultiplications) {
|
||||
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
|
||||
} else {
|
||||
TODO("var mul sbyte litval $value")
|
||||
// asmgen.out(" jsr prog8_lib.mul_byte")
|
||||
}
|
||||
}
|
||||
if(value in asmgen.optimizedByteMultiplications)
|
||||
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
|
||||
else
|
||||
asmgen.out(" lda $name | ldy #$value | jsr math.multiply_bytes | sta $name")
|
||||
}
|
||||
"/" -> {
|
||||
if (dt == DataType.UBYTE) {
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy #$value
|
||||
jsr math.divmod_ub_asm
|
||||
sty $name
|
||||
""")
|
||||
} else {
|
||||
TODO("var BYTE div litval")
|
||||
}
|
||||
if (dt == DataType.UBYTE)
|
||||
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_ub_asm | sty $name")
|
||||
else
|
||||
asmgen.out(" lda $name | ldy #$value | jsr math.divmod_b_asm | sty $name")
|
||||
}
|
||||
"%" -> {
|
||||
if(dt==DataType.BYTE)
|
||||
@ -609,14 +577,30 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta $name""")
|
||||
}
|
||||
"<<" -> {
|
||||
repeat(value) { asmgen.out(" asl $name") }
|
||||
if(value>=8) asmgen.out(" lda #0 | sta $name")
|
||||
else repeat(value) { asmgen.out(" asl $name") }
|
||||
}
|
||||
">>" -> {
|
||||
if(value>0) {
|
||||
if (dt == DataType.UBYTE) {
|
||||
repeat(value) { asmgen.out(" lsr $name") }
|
||||
if(value>=8) asmgen.out(" lda #0 | sta $name")
|
||||
else repeat(value) { asmgen.out(" lsr $name") }
|
||||
} else {
|
||||
repeat(value) { asmgen.out(" lda $name | asl a | ror $name") }
|
||||
when {
|
||||
value>=8 -> asmgen.out("""
|
||||
lda $name
|
||||
bmi +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #-1
|
||||
+ sta $name""")
|
||||
value>3 -> asmgen.out("""
|
||||
lda $name
|
||||
ldy #$value
|
||||
jsr math.lsr_byte_A
|
||||
sta $name""")
|
||||
else -> repeat(value) { asmgen.out(" lda $name | asl a | ror $name") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -644,10 +628,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
sta $name""")
|
||||
// TODO: more operators
|
||||
// TODO: tuned code for more operators
|
||||
}
|
||||
else -> {
|
||||
inplaceModification_byte_value_to_variable(name, dt, operator, memread);
|
||||
inplaceModification_byte_value_to_variable(name, dt, operator, memread)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -675,10 +659,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
bcc +
|
||||
dec $name+1
|
||||
+""")
|
||||
// TODO: more operators
|
||||
// TODO: tuned code for more operators
|
||||
}
|
||||
else -> {
|
||||
inplaceModification_word_value_to_variable(name, dt, operator, memread);
|
||||
inplaceModification_word_value_to_variable(name, dt, operator, memread)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -686,7 +670,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private fun inplaceModification_word_litval_to_variable(name: String, dt: DataType, operator: String, value: Int) {
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
// TODO use these + and - optimizations in the expressionAsmGenerator as well.
|
||||
"+" -> {
|
||||
when {
|
||||
value==0 -> {}
|
||||
@ -701,6 +684,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
value==0x0100 -> asmgen.out(" inc $name+1")
|
||||
value==0x0200 -> asmgen.out(" inc $name+1 | inc $name+1")
|
||||
value==0x0300 -> asmgen.out(" inc $name+1 | inc $name+1 | inc $name+1")
|
||||
value==0x0400 -> asmgen.out(" inc $name+1 | inc $name+1 | inc $name+1 | inc $name+1")
|
||||
value and 255==0 -> asmgen.out(" lda $name+1 | clc | adc #>$value | sta $name+1")
|
||||
else -> asmgen.out("""
|
||||
lda $name
|
||||
@ -726,6 +710,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
value==0x0100 -> asmgen.out(" dec $name+1")
|
||||
value==0x0200 -> asmgen.out(" dec $name+1 | dec $name+1")
|
||||
value==0x0300 -> asmgen.out(" dec $name+1 | dec $name+1 | dec $name+1")
|
||||
value==0x0400 -> asmgen.out(" dec $name+1 | dec $name+1 | dec $name+1 | dec $name+1")
|
||||
value and 255==0 -> asmgen.out(" lda $name+1 | sec | sbc #>$value | sta $name+1")
|
||||
else -> asmgen.out("""
|
||||
lda $name
|
||||
@ -738,41 +723,22 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
"*" -> {
|
||||
if(dt == DataType.UWORD){
|
||||
if(value in asmgen.optimizedWordMultiplications) {
|
||||
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1""")
|
||||
}
|
||||
// the mul code works for both signed and unsigned
|
||||
if(value in asmgen.optimizedWordMultiplications) {
|
||||
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
|
||||
} else {
|
||||
if(value.absoluteValue in asmgen.optimizedWordMultiplications) {
|
||||
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
|
||||
} else {
|
||||
// TODO does this work for signed words? if so the uword/word distinction can be removed altogether
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1""")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1""")
|
||||
}
|
||||
}
|
||||
"/" -> {
|
||||
@ -825,14 +791,63 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
""")
|
||||
}
|
||||
"<<" -> {
|
||||
repeat(value) { asmgen.out(" asl $name | rol $name+1") }
|
||||
when {
|
||||
value>=16 -> asmgen.out(" lda #0 | sta $name | sta $name+1")
|
||||
value==8 -> asmgen.out(" lda $name | sta $name+1 | lda #0 | sta $name")
|
||||
value>2 -> asmgen.out("""
|
||||
ldy #$value
|
||||
- asl $name
|
||||
rol $name+1
|
||||
dey
|
||||
bne -
|
||||
""")
|
||||
else -> repeat(value) { asmgen.out(" asl $name | rol $name+1") }
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
if (value > 0) {
|
||||
if(dt==DataType.UWORD) {
|
||||
repeat(value) { asmgen.out(" lsr $name+1 | ror $name")}
|
||||
when {
|
||||
value>=16 -> asmgen.out(" lda #0 | sta $name | sta $name+1")
|
||||
value==8 -> asmgen.out(" lda $name+1 | sta $name | lda #0 | sta $name+1")
|
||||
value>2 -> asmgen.out("""
|
||||
ldy #$value
|
||||
- lsr $name+1
|
||||
ror $name
|
||||
dey
|
||||
bne -""")
|
||||
else -> repeat(value) { asmgen.out(" lsr $name+1 | ror $name")}
|
||||
}
|
||||
} else {
|
||||
repeat(value) { asmgen.out(" lda $name+1 | asl a | ror $name+1 | ror $name") }
|
||||
when {
|
||||
value>=16 -> asmgen.out("""
|
||||
lda $name+1
|
||||
bmi +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #-1
|
||||
+ sta $name
|
||||
sta $name+1""")
|
||||
value==8 -> asmgen.out("""
|
||||
lda $name+1
|
||||
sta $name
|
||||
bmi +
|
||||
lda #0
|
||||
- sta $name+1
|
||||
beq ++
|
||||
+ lda #-1
|
||||
sta $name+1
|
||||
+""")
|
||||
value>2 -> asmgen.out("""
|
||||
ldy #$value
|
||||
- lda $name+1
|
||||
asl a
|
||||
ror $name+1
|
||||
ror $name
|
||||
dey
|
||||
bne -""")
|
||||
else -> repeat(value) { asmgen.out(" lda $name+1 | asl a | ror $name+1 | ror $name") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -956,33 +971,43 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- asl $name
|
||||
rol $name+1
|
||||
dey
|
||||
bne -""")
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
">>" -> {
|
||||
if(dt==DataType.UWORD) {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- lsr $name+1
|
||||
ror $name
|
||||
dey
|
||||
bne -""")
|
||||
bne -
|
||||
+""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
beq +
|
||||
- lda $name+1
|
||||
asl a
|
||||
ror $name+1
|
||||
ror $name
|
||||
dey
|
||||
bne -""")
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
}
|
||||
"&" -> TODO("bitand (u)wordvar bytevar")
|
||||
"^" -> TODO("bitxor (u)wordvar bytevar")
|
||||
"|" -> TODO("bitor (u)wordvar bytevar")
|
||||
"&" -> {
|
||||
asmgen.out(" lda $otherName | and $name | sta $name")
|
||||
if(dt in WordDatatypes)
|
||||
asmgen.out(" lda #0 | sta $name+1")
|
||||
}
|
||||
"^" -> asmgen.out(" lda $otherName | eor $name | sta $name")
|
||||
"|" -> asmgen.out(" lda $otherName | ora $name | sta $name")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -1068,10 +1093,73 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private fun inplaceModification_word_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) {
|
||||
// this should be the last resort for code generation for this,
|
||||
// because the value is evaluated onto the eval stack (=slow).
|
||||
println("warning: slow stack evaluation used (4): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
|
||||
if(asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used (4): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
|
||||
asmgen.translateExpression(value)
|
||||
val valueDt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
|
||||
fun multiplyWord() {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
ldy P8ESTACK_HI+1,x
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1
|
||||
""")
|
||||
}
|
||||
|
||||
fun divideWord() {
|
||||
if (dt == DataType.WORD) {
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda P8ESTACK_LO+1,x
|
||||
ldy P8ESTACK_HI+1,x
|
||||
jsr math.divmod_w_asm
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda P8ESTACK_LO+1,x
|
||||
ldy P8ESTACK_HI+1,x
|
||||
jsr math.divmod_uw_asm
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
}
|
||||
}
|
||||
|
||||
fun remainderWord() {
|
||||
if(dt==DataType.WORD)
|
||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda P8ESTACK_LO+1,x
|
||||
ldy P8ESTACK_HI+1,x
|
||||
jsr math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $name
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $name+1
|
||||
""")
|
||||
}
|
||||
|
||||
when(valueDt) {
|
||||
in ByteDatatypes -> {
|
||||
// the other variable is a BYTE type so optimize for that
|
||||
@ -1125,27 +1213,35 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
sta $name+1""")
|
||||
}
|
||||
"*" -> TODO("mul (u)word (u)byte")
|
||||
"/" -> TODO("div (u)word (u)byte")
|
||||
"%" -> TODO("(u)word remainder (u)byte")
|
||||
"*" -> {
|
||||
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
|
||||
asmgen.signExtendStackLsb(valueDt)
|
||||
multiplyWord()
|
||||
}
|
||||
"/" -> {
|
||||
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
|
||||
asmgen.signExtendStackLsb(valueDt)
|
||||
divideWord()
|
||||
}
|
||||
"%" -> {
|
||||
// stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
|
||||
asmgen.signExtendStackLsb(valueDt)
|
||||
remainderWord()
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.out("""
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
beq +
|
||||
- asl $name
|
||||
rol $name+1
|
||||
dey
|
||||
bne -
|
||||
ldy P8ESTACK_LO+1,x
|
||||
beq +
|
||||
- asl $name
|
||||
rol $name+1
|
||||
dey
|
||||
bne -
|
||||
+""")
|
||||
}
|
||||
">>" -> {
|
||||
asmgen.translateExpression(value)
|
||||
if(dt==DataType.UWORD) {
|
||||
asmgen.out("""
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
ldy P8ESTACK_LO+1,x
|
||||
beq +
|
||||
- lsr $name+1
|
||||
ror $name
|
||||
@ -1154,8 +1250,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""") }
|
||||
else {
|
||||
asmgen.out("""
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
ldy P8ESTACK_LO+1,x
|
||||
beq +
|
||||
- lda $name+1
|
||||
asl a
|
||||
@ -1166,9 +1261,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
}
|
||||
"&" -> TODO("bitand (u)word (u)byte")
|
||||
"^" -> TODO("bitxor (u)word (u)byte")
|
||||
"|" -> TODO("bitor (u)word (u)byte")
|
||||
"&" -> {
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x | and $name | sta $name")
|
||||
if(dt in WordDatatypes)
|
||||
asmgen.out(" lda #0 | sta $name+1")
|
||||
}
|
||||
"^" -> asmgen.out(" lda P8ESTACK_LO+1,x | eor $name | sta $name")
|
||||
"|" -> asmgen.out(" lda P8ESTACK_LO+1,x | ora $name | sta $name")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -1178,65 +1277,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> asmgen.out(" lda $name | clc | adc P8ESTACK_LO+1,x | sta $name | lda $name+1 | adc P8ESTACK_HI+1,x | sta $name+1")
|
||||
"-" -> asmgen.out(" lda $name | sec | sbc P8ESTACK_LO+1,x | sta $name | lda $name+1 | sbc P8ESTACK_HI+1,x | sta $name+1")
|
||||
"*" -> {
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
ldy P8ESTACK_HI+1,x
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1
|
||||
""")
|
||||
}
|
||||
"/" -> {
|
||||
if (dt == DataType.WORD) {
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda P8ESTACK_LO+1,x
|
||||
ldy P8ESTACK_HI+1,x
|
||||
jsr math.divmod_w_asm
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda P8ESTACK_LO+1,x
|
||||
ldy P8ESTACK_HI+1,x
|
||||
jsr math.divmod_uw_asm
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
}
|
||||
}
|
||||
"%" -> {
|
||||
if(dt==DataType.WORD)
|
||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda P8ESTACK_LO+1,x
|
||||
ldy P8ESTACK_HI+1,x
|
||||
jsr math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $name
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $name+1
|
||||
""")
|
||||
}
|
||||
"*" -> multiplyWord()
|
||||
"/" -> divideWord()
|
||||
"%" -> remainderWord()
|
||||
"<<", ">>" -> throw AssemblyError("shift by a word value not supported, max is a byte")
|
||||
"&" -> asmgen.out(" lda $name | and P8ESTACK_LO+1,x | sta $name | lda $name+1 | and P8ESTACK_HI+1,x | sta $name+1")
|
||||
"^" -> asmgen.out(" lda $name | eor P8ESTACK_LO+1,x | sta $name | lda $name+1 | eor P8ESTACK_HI+1,x | sta $name+1")
|
||||
@ -1252,13 +1295,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out(" inx")
|
||||
}
|
||||
|
||||
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: Expression) {
|
||||
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: Expression, scope: Subroutine?) {
|
||||
// this should be the last resort for code generation for this,
|
||||
// because the value is evaluated onto the eval stack (=slow).
|
||||
println("warning: slow stack evaluation used (2): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
|
||||
if(asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used (2): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.out(" jsr floats.pop_float_fac1")
|
||||
asmgen.saveRegister(CpuRegister.X)
|
||||
asmgen.saveRegister(CpuRegister.X, false, scope)
|
||||
when (operator) {
|
||||
"**" -> {
|
||||
asmgen.out("""
|
||||
@ -1303,16 +1347,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
ldy #>$name
|
||||
jsr floats.MOVMF
|
||||
""")
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
asmgen.restoreRegister(CpuRegister.X, false)
|
||||
}
|
||||
|
||||
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference) {
|
||||
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference, scope: Subroutine?) {
|
||||
val valueDt = ident.targetVarDecl(program.namespace)!!.datatype
|
||||
if(valueDt != DataType.FLOAT)
|
||||
throw AssemblyError("float variable expected")
|
||||
|
||||
val otherName = asmgen.asmVariableName(ident)
|
||||
asmgen.saveRegister(CpuRegister.X)
|
||||
asmgen.saveRegister(CpuRegister.X, false, scope)
|
||||
when (operator) {
|
||||
"**" -> {
|
||||
asmgen.out("""
|
||||
@ -1372,12 +1416,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
ldy #>$name
|
||||
jsr floats.MOVMF
|
||||
""")
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
asmgen.restoreRegister(CpuRegister.X, false)
|
||||
}
|
||||
|
||||
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double) {
|
||||
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: Subroutine?) {
|
||||
val constValueName = asmgen.getFloatAsmConst(value)
|
||||
asmgen.saveRegister(CpuRegister.X)
|
||||
asmgen.saveRegister(CpuRegister.X, false, scope)
|
||||
when (operator) {
|
||||
"**" -> {
|
||||
asmgen.out("""
|
||||
@ -1444,7 +1488,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
ldy #>$name
|
||||
jsr floats.MOVMF
|
||||
""")
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
asmgen.restoreRegister(CpuRegister.X, false)
|
||||
}
|
||||
|
||||
private fun inplaceCast(target: AsmAssignTarget, cast: TypecastExpression, position: Position) {
|
||||
@ -1537,7 +1581,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
else -> {
|
||||
println("warning: slow stack evaluation used (6): ${mem.addressExpression::class.simpleName} at ${mem.addressExpression.position}") // TODO
|
||||
if(asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used (6): ${mem.addressExpression::class.simpleName} at ${mem.addressExpression.position}") // TODO
|
||||
asmgen.translateExpression(mem.addressExpression)
|
||||
asmgen.out("""
|
||||
jsr prog8_lib.read_byte_from_address_on_stack
|
||||
@ -1606,7 +1651,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
else -> {
|
||||
println("warning: slow stack evaluation used (7): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO
|
||||
if(asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used (7): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO
|
||||
asmgen.translateExpression(memory.addressExpression)
|
||||
asmgen.out("""
|
||||
jsr prog8_lib.read_byte_from_address_on_stack
|
||||
|
@ -101,7 +101,8 @@ val BuiltinFunctions = mapOf(
|
||||
"rightstr" to FSignature(false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null)
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
"strcmp" to FSignature(false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
|
||||
)
|
||||
|
||||
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
|
||||
@ -285,9 +286,9 @@ private fun builtinStrlen(args: List<Expression>, position: Position, program: P
|
||||
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
|
||||
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
|
||||
if(vardecl!=null) {
|
||||
if(vardecl.datatype!=DataType.STR)
|
||||
if(vardecl.datatype!=DataType.STR && vardecl.datatype!=DataType.UWORD)
|
||||
throw SyntaxError("strlen must have string argument", position)
|
||||
if(vardecl.autogeneratedDontRemove) {
|
||||
if(vardecl.autogeneratedDontRemove && vardecl.value!=null) {
|
||||
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
|
||||
}
|
||||
}
|
||||
|
80
compiler/src/prog8/optimizer/BinExprSplitter.kt
Normal file
80
compiler/src/prog8/optimizer/BinExprSplitter.kt
Normal file
@ -0,0 +1,80 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
|
||||
|
||||
internal class BinExprSplitter(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
|
||||
// if(decl.type==VarDeclType.VAR ) {
|
||||
// val binExpr = decl.value as? BinaryExpression
|
||||
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
|
||||
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
|
||||
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
|
||||
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
// val assign = Assignment(target, augExpr, binExpr.position)
|
||||
// println("SPLIT VARDECL $decl")
|
||||
// return listOf(
|
||||
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
|
||||
// IAstModification.InsertAfter(decl, assign, parent)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// return noModifications
|
||||
// }
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
/*
|
||||
|
||||
reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
||||
by attempting to splitting it up into individual simple steps:
|
||||
|
||||
|
||||
X = BinExpr X = LeftExpr
|
||||
<operator> followed by
|
||||
/ \ IF 'X' not used X = BinExpr
|
||||
/ \ IN LEFTEXPR ==> <operator>
|
||||
/ \ / \
|
||||
LeftExpr. RightExpr. / \
|
||||
/ \ / \ X RightExpr.
|
||||
.. .. .. ..
|
||||
|
||||
*/
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
|
||||
if (!assignment.isAugmentable) {
|
||||
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, firstAssign, assignment.definingScope()),
|
||||
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO further unraveling of binary expression trees into flat statements.
|
||||
// however this should probably be done in a more generic way to also service
|
||||
// the expressiontrees that are not used in an assignment statement...
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope) =
|
||||
if (target.identifier!=null || target.memoryAddress!=null || target.arrayindexed!=null)
|
||||
target.isInRegularRAM(namespace)
|
||||
else
|
||||
false
|
||||
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
@ -25,7 +24,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
|
||||
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
|
||||
@ -79,8 +78,10 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
|
||||
} else if (directive.directive == "%asminclude") {
|
||||
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
|
||||
val scope = directive.definingScope()
|
||||
scanAssemblyCode(asm, directive, scope)
|
||||
val scope = directive.definingSubroutine()
|
||||
if(scope!=null) {
|
||||
scanAssemblyCode(asm, directive, scope)
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(directive)
|
||||
@ -167,12 +168,12 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(inlineAssembly: InlineAssembly) {
|
||||
// parse inline asm for subroutine calls (jmp, jsr)
|
||||
val scope = inlineAssembly.definingScope()
|
||||
val scope = inlineAssembly.definingSubroutine()
|
||||
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
|
||||
super.visit(inlineAssembly)
|
||||
}
|
||||
|
||||
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
|
||||
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
|
||||
asm.lines().forEach { line ->
|
||||
val matches = asmJumpRx.matchEntire(line)
|
||||
if (matches != null) {
|
||||
@ -180,13 +181,15 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
|
||||
val node = program.namespace.lookup(jumptarget.split('.'), context)
|
||||
if (node is Subroutine) {
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
} else if (jumptarget.contains('.')) {
|
||||
// maybe only the first part already refers to a subroutine
|
||||
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
|
||||
if (node2 is Subroutine) {
|
||||
calls[scope] = calls.getValue(scope).plus(node2)
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node2)
|
||||
calledBy[node2] = calledBy.getValue(node2).plus(context)
|
||||
}
|
||||
}
|
||||
@ -199,7 +202,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
if (target.contains('.')) {
|
||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||
if (node is Subroutine) {
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
}
|
||||
}
|
||||
@ -208,4 +212,55 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkRecursiveCalls(errors: ErrorReporter) {
|
||||
val cycles = recursionCycles()
|
||||
if(cycles.any()) {
|
||||
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)
|
||||
val printed = mutableSetOf<Subroutine>()
|
||||
for(chain in cycles) {
|
||||
if(chain[0] !in printed) {
|
||||
val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||
errors.warn("Cycle in (a subroutine call in) $chainStr", Position.DUMMY)
|
||||
printed.add(chain[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recursionCycles(): List<List<Subroutine>> {
|
||||
val chains = mutableListOf<MutableList<Subroutine>>()
|
||||
for(caller in calls.keys) {
|
||||
val visited = calls.keys.associateWith { false }.toMutableMap()
|
||||
val recStack = calls.keys.associateWith { false }.toMutableMap()
|
||||
val chain = mutableListOf<Subroutine>()
|
||||
if(hasCycle(caller, visited, recStack, chain))
|
||||
chains.add(chain)
|
||||
}
|
||||
return chains
|
||||
}
|
||||
|
||||
private fun hasCycle(sub: Subroutine, visited: MutableMap<Subroutine, Boolean>, recStack: MutableMap<Subroutine, Boolean>, chain: MutableList<Subroutine>): Boolean {
|
||||
// mark current node as visited and add to recursion stack
|
||||
if(recStack[sub]==true)
|
||||
return true
|
||||
if(visited[sub]==true)
|
||||
return false
|
||||
|
||||
// mark visited and add to recursion stack
|
||||
visited[sub] = true
|
||||
recStack[sub] = true
|
||||
|
||||
// recurse for all neighbours
|
||||
for(called in calls.getValue(sub)) {
|
||||
if(hasCycle(called, visited, recStack, chain)) {
|
||||
chain.add(called)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// pop from recursion stack
|
||||
recStack[sub] = false
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
|
||||
// Fix up the literal value's type to match that of the vardecl
|
||||
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
|
||||
internal class VarConstantValueTypeAdjuster(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
@ -58,15 +58,16 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
|
||||
if(decl.isArray){
|
||||
if(decl.arraysize==null) {
|
||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||
val arraysize = decl.arraysize
|
||||
if(arraysize==null) {
|
||||
// for arrays that have no size specifier attempt to deduce the size
|
||||
val arrayval = decl.value as? ArrayLiteralValue
|
||||
if(arrayval!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
@ -75,14 +76,13 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
decl
|
||||
))
|
||||
}
|
||||
}
|
||||
else if(decl.arraysize?.constIndex()==null) {
|
||||
val size = decl.arraysize!!.index.constValue(program)
|
||||
if(size!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
size, decl
|
||||
))
|
||||
} else if(arraysize.constIndex()==null) {
|
||||
// see if we can calculate the size from other fields
|
||||
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
|
||||
if(cval!=null) {
|
||||
arraysize.indexVar = null
|
||||
arraysize.origExpression = null
|
||||
arraysize.indexNum = cval
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,11 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.VarDecl
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
@ -279,80 +275,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
|
||||
// if(decl.type==VarDeclType.VAR ) {
|
||||
// val binExpr = decl.value as? BinaryExpression
|
||||
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
|
||||
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
|
||||
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
|
||||
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
// val assign = Assignment(target, augExpr, binExpr.position)
|
||||
// println("SPLIT VARDECL $decl")
|
||||
// return listOf(
|
||||
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
|
||||
// IAstModification.InsertAfter(decl, assign, parent)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// return noModifications
|
||||
// }
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
/*
|
||||
|
||||
reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
||||
by attempting to splitting it up into individual simple steps:
|
||||
|
||||
|
||||
X = BinExpr X = LeftExpr
|
||||
<operator> followed by
|
||||
/ \ IF 'X' not used X = BinExpr
|
||||
/ \ IN LEFTEXPR ==> <operator>
|
||||
/ \ / \
|
||||
LeftExpr. RightExpr. / \
|
||||
/ \ / \ X RightExpr.
|
||||
.. .. .. ..
|
||||
|
||||
*/
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
|
||||
if (!assignment.isAugmentable) {
|
||||
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, firstAssign, parent),
|
||||
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO further unraveling of binary expression trees into flat statements.
|
||||
// however this should probably be done in a more generic way to also service
|
||||
// the expressiontrees that are not used in an assignment statement...
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope): Boolean {
|
||||
return when {
|
||||
target.identifier!=null -> target.isInRegularRAM(namespace)
|
||||
target.memoryAddress!=null -> target.isInRegularRAM(namespace)
|
||||
target.arrayindexed!=null -> {
|
||||
val index = target.arrayindexed!!.arrayspec.index
|
||||
if(index is NumericLiteralValue)
|
||||
target.isInRegularRAM(namespace)
|
||||
else
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCall.target.nameInSource == listOf("lsb")) {
|
||||
val arg = functionCall.args[0]
|
||||
@ -429,6 +351,13 @@ X = BinExpr X = LeftExpr
|
||||
}
|
||||
// no need to check for left val constant (because of associativity)
|
||||
|
||||
val rnum = rightVal?.number?.toDouble()
|
||||
if(rnum!=null && rnum<0.0) {
|
||||
expr.operator = "-"
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||
return expr
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@ -443,12 +372,16 @@ X = BinExpr X = LeftExpr
|
||||
|
||||
if (rightVal != null) {
|
||||
// right value is a constant, see if we can optimize
|
||||
val rightConst: NumericLiteralValue = rightVal
|
||||
when (rightConst.number.toDouble()) {
|
||||
0.0 -> {
|
||||
// left
|
||||
return expr.left
|
||||
}
|
||||
val rnum = rightVal.number.toDouble()
|
||||
if (rnum == 0.0) {
|
||||
// left
|
||||
return expr.left
|
||||
}
|
||||
|
||||
if(rnum<0.0) {
|
||||
expr.operator = "+"
|
||||
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
|
||||
return expr
|
||||
}
|
||||
}
|
||||
if (leftVal != null) {
|
||||
@ -461,6 +394,7 @@ X = BinExpr X = LeftExpr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@ -683,6 +617,7 @@ X = BinExpr X = LeftExpr
|
||||
if (amount >= 16) {
|
||||
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
// TODO is this correct???
|
||||
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
|
||||
if (amount == 8) {
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
@ -722,8 +657,9 @@ X = BinExpr X = LeftExpr
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
} else if (amount >= 8) {
|
||||
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||
if (amount == 8)
|
||||
return msb
|
||||
if (amount == 8) {
|
||||
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
|
||||
}
|
||||
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
@ -731,14 +667,6 @@ X = BinExpr X = LeftExpr
|
||||
if (amount > 16) {
|
||||
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
|
||||
return null
|
||||
} else if (amount >= 8) {
|
||||
val msbAsByte = TypecastExpression(
|
||||
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
|
||||
DataType.BYTE,
|
||||
true, expr.position)
|
||||
if (amount == 8)
|
||||
return msbAsByte
|
||||
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
|
@ -5,7 +5,7 @@ import prog8.ast.base.ErrorReporter
|
||||
|
||||
|
||||
internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
|
||||
val valuetypefixer = VarConstantValueTypeAdjuster(this)
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
valuetypefixer.applyModifications()
|
||||
@ -53,3 +53,9 @@ internal fun Program.simplifyExpressions() : Int {
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.splitBinaryExpressions() : Int {
|
||||
val opti = BinExprSplitter(this)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
@ -25,12 +25,12 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent))
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
|
||||
if (block !in callgraph.usedSymbols) {
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent))
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -42,16 +42,16 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if(subroutine.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
val removals = callgraph.calledBy.getValue(subroutine).map {
|
||||
IAstModification.Remove(it, it.parent)
|
||||
IAstModification.Remove(it, it.definingScope())
|
||||
}.toMutableList()
|
||||
removals += IAstModification.Remove(subroutine, parent)
|
||||
removals += IAstModification.Remove(subroutine, subroutine.definingScope())
|
||||
return removals
|
||||
}
|
||||
}
|
||||
|
||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
return listOf(IAstModification.Remove(subroutine, parent))
|
||||
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
@ -63,7 +63,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if(decl.type == VarDeclType.VAR)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
|
||||
return listOf(IAstModification.Remove(decl, parent))
|
||||
return listOf(IAstModification.Remove(decl, decl.definingScope()))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
@ -74,7 +74,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in pureBuiltinFunctions) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent))
|
||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,7 +127,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent))
|
||||
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
@ -150,7 +150,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty if statements
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
|
||||
return listOf(IAstModification.Remove(ifStatement, parent))
|
||||
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope()))
|
||||
|
||||
// empty true part? switch with the else part
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
||||
@ -183,12 +183,12 @@ internal class StatementOptimizer(private val program: Program,
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(forLoop.body.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty for loop", forLoop.position)
|
||||
return listOf(IAstModification.Remove(forLoop, parent))
|
||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
|
||||
} else if(forLoop.body.statements.size==1) {
|
||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
||||
// remove empty for loop (only loopvar decl in it)
|
||||
return listOf(IAstModification.Remove(forLoop, parent))
|
||||
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,7 +265,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
} else {
|
||||
// always false -> remove the while statement altogether
|
||||
errors.warn("condition is always false", whileLoop.condition.position)
|
||||
listOf(IAstModification.Remove(whileLoop, parent))
|
||||
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope()))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -276,12 +276,12 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if(iter!=null) {
|
||||
if(repeatLoop.body.containsNoCodeNorVars()) {
|
||||
errors.warn("empty loop removed", repeatLoop.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, parent))
|
||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
|
||||
}
|
||||
val iterations = iter.constValue(program)?.number?.toInt()
|
||||
if (iterations == 0) {
|
||||
errors.warn("iterations is always 0, removed loop", iter.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, parent))
|
||||
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
|
||||
}
|
||||
if (iterations == 1) {
|
||||
errors.warn("iterations is always 1", iter.position)
|
||||
@ -308,7 +308,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
val scope = jump.definingScope()
|
||||
val label = jump.identifier?.targetStatement(scope)
|
||||
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
||||
return listOf(IAstModification.Remove(jump, parent))
|
||||
return listOf(IAstModification.Remove(jump, jump.definingScope()))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
@ -341,7 +341,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, addConstant, parent))
|
||||
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope()))
|
||||
} else if (op2 == "-") {
|
||||
// A = A +/- B - N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
@ -352,7 +352,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, subConstant, parent))
|
||||
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -374,7 +374,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
// remove assignment to self
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
}
|
||||
|
||||
val targetIDt = assignment.target.inferType(program, assignment)
|
||||
@ -394,7 +394,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
||||
@ -408,7 +408,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
"-" -> {
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
||||
@ -420,18 +420,18 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
"<<" -> {
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
}
|
||||
">>" -> {
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,20 +20,20 @@ internal class UnusedCodeRemover(private val program: Program, private val error
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
|
||||
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
|
||||
removals.add(IAstModification.Remove(sub, sub.definingScope()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
|
||||
removals.add(IAstModification.Remove(block, block.definingScope()))
|
||||
}
|
||||
|
||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||
program.modules.forEach {
|
||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||
removals.add(IAstModification.Remove(it, it.parent))
|
||||
removals.add(IAstModification.Remove(it, it.definingScope()))
|
||||
}
|
||||
|
||||
return removals
|
||||
@ -91,8 +91,10 @@ internal class UnusedCodeRemover(private val program: Program, private val error
|
||||
val assign1 = stmtPairs[0] as? Assignment
|
||||
val assign2 = stmtPairs[1] as? Assignment
|
||||
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
|
||||
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace))
|
||||
linesToRemove.add(assign1)
|
||||
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace)) {
|
||||
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
|
||||
linesToRemove.add(assign1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ class TestC64Zeropage {
|
||||
@Test
|
||||
fun testFreeSpaces() {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(16, zp1.available())
|
||||
assertEquals(18, zp1.available())
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
|
||||
assertEquals(89, zp2.available())
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
|
||||
@ -220,7 +220,7 @@ class TestC64Zeropage {
|
||||
@Test
|
||||
fun testBasicsafeAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(16, zp.available())
|
||||
assertEquals(18, zp.available())
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
// in regular zp there aren't 5 sequential bytes free
|
||||
@ -273,16 +273,18 @@ class TestC64Zeropage {
|
||||
@Test
|
||||
fun testEfficientAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(16, zp.available())
|
||||
assertEquals(18, zp.available())
|
||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x9b, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
|
||||
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
assertEquals(0, zp.available())
|
||||
}
|
||||
|
BIN
docs/source/_static/primes_cx16.png
Normal file
BIN
docs/source/_static/primes_cx16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -41,7 +41,7 @@ of that build task, you can start the compiler with:
|
||||
|
||||
(You should probably make an alias...)
|
||||
|
||||
.. note::
|
||||
.. hint::
|
||||
Development and testing is done on Linux, but the compiler should run on most
|
||||
operating systems. If you do have trouble building or running
|
||||
the compiler on another operating system, please let me know!
|
||||
|
@ -96,6 +96,12 @@ when compiled an ran on a C-64 you get this:
|
||||
:align: center
|
||||
:alt: result when run on C-64
|
||||
|
||||
when the exact same program is compiled for the Commander X16 target, and run on the emulator, you get this:
|
||||
|
||||
.. image:: _static/primes_cx16.png
|
||||
:align: center
|
||||
:alt: result when run on CX16 emulator
|
||||
|
||||
|
||||
|
||||
Design principles and features
|
||||
@ -165,6 +171,7 @@ If you're targeting the CommanderX16, there's the `x16emu <https://github.com/co
|
||||
building.rst
|
||||
programming.rst
|
||||
syntaxreference.rst
|
||||
libraries.rst
|
||||
todo.rst
|
||||
|
||||
|
||||
|
94
docs/source/libraries.rst
Normal file
94
docs/source/libraries.rst
Normal file
@ -0,0 +1,94 @@
|
||||
************************
|
||||
Compiler library modules
|
||||
************************
|
||||
|
||||
The compiler provides several "built-in" library modules with useful subroutine and variables.
|
||||
|
||||
Some of these may be specific for a certain compilation target, or work slightly different,
|
||||
but some effort is put into making them available across compilation targets.
|
||||
|
||||
This means that as long as your program is only using the subroutines from these
|
||||
libraries and not using hardware- and/or system dependent code, and isn't hardcoding certain
|
||||
assumptions like the screen size, the exact same source program can
|
||||
be compiled for multiple different target platforms. Many of the example programs that come
|
||||
with Prog8 are written like this.
|
||||
|
||||
You can ``%import`` and use these modules explicitly, but the compiler may also import one or more
|
||||
of these library modules automatically as required.
|
||||
|
||||
.. caution::
|
||||
The resulting compiled binary program *only works on the target machine it was compiled for*.
|
||||
You must recompile the program for every target you want to run it on.
|
||||
|
||||
|
||||
|
||||
syslib
|
||||
------
|
||||
The "system library" for your target machine. It contains many system-specific definitions such
|
||||
as ROM/kernal subroutine definitions, memory location constants, and utility subroutines.
|
||||
|
||||
Many of these definitions overlap for the C64 and Commander X16 targets so it is still possible
|
||||
to write programs that work on both targets without modifications.
|
||||
|
||||
conv
|
||||
----
|
||||
Routines to convert strings to numbers or vice versa.
|
||||
|
||||
- numbers to strings, in various formats (binary, hex, decimal)
|
||||
- strings in decimal, hex and binary format into numbers
|
||||
|
||||
|
||||
textio (txt.*)
|
||||
--------------
|
||||
This will probably be the most used library module. It contains a whole lot of routines
|
||||
dealing with text-based input and output (to the screen). Such as
|
||||
|
||||
- printing strings and numbers
|
||||
- reading text input from the user via the keyboard
|
||||
- filling or clearing the screen and colors
|
||||
- scrolling the text on the screen
|
||||
- placing individual characters on the screen
|
||||
|
||||
|
||||
diskio
|
||||
------
|
||||
Provides several routines that deal with disk drive I/O, such as:
|
||||
|
||||
- show directory
|
||||
- display disk drive status
|
||||
- load and save data from and to the disk
|
||||
- delete and rename files on the disk
|
||||
|
||||
|
||||
floats
|
||||
------
|
||||
Provides definitions for the ROM/kernel subroutines and utility routines dealing with floating
|
||||
point variables. This includes ``print_f``, the routine used to print floating point numbers.
|
||||
|
||||
|
||||
graphics
|
||||
--------
|
||||
High-res monochrome bitmap graphics routines:
|
||||
|
||||
- clearing the screen
|
||||
- drawing lines
|
||||
- drawing circles and discs (filled circles)
|
||||
- plotting individual pixels
|
||||
|
||||
|
||||
math
|
||||
----
|
||||
Low level math routines. You should not normally have to bother with this directly.
|
||||
The compiler needs it to implement most of the math operations in your programs.
|
||||
|
||||
|
||||
cx16logo
|
||||
--------
|
||||
A 'fun' module that contains the Commander X16 logo and that allows you
|
||||
to print it anywhere on the screen.
|
||||
|
||||
|
||||
prog8_lib
|
||||
---------
|
||||
Low level language support. You should not normally have to bother with this directly.
|
||||
The compiler needs it for verious built-in system routines.
|
@ -260,6 +260,12 @@ Note that the various keywords for the data type and variable type (``byte``, ``
|
||||
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
|
||||
for instance.
|
||||
|
||||
|
||||
It's possible to assign a new array to another array, this will overwrite all elements in the original
|
||||
array with those in the value array. The number and types of elements have to match.
|
||||
For large arrays this is a slow operation because every element is copied over. It should probably be avoided.
|
||||
|
||||
|
||||
**Arrays at a specific memory location:**
|
||||
Using the memory-mapped syntax it is possible to define an array to be located at a specific memory location.
|
||||
For instance to reference the first 5 rows of the Commodore 64's screen matrix as an array, you can define::
|
||||
@ -287,7 +293,7 @@ This @-prefix can also be used for character byte values.
|
||||
You can concatenate two string literals using '+' (not very useful though) or repeat
|
||||
a string literal a given number of times using '*'. You can also assign a new string
|
||||
value to another string. No bounds check is done so be sure the destination string is
|
||||
large enough to contain the new value::
|
||||
large enough to contain the new value (it is overwritten in memory)::
|
||||
|
||||
str string1 = "first part" + "second part"
|
||||
str string2 = "hello!" * 10
|
||||
@ -296,7 +302,13 @@ large enough to contain the new value::
|
||||
string1 = "new value"
|
||||
|
||||
|
||||
.. info::
|
||||
There are several 'escape sequences' to help you put special characters into strings, such
|
||||
as newlines, quote characters themselves, and so on. The ones used most often are
|
||||
``\\``, ``\"``, ``\n``, ``\r``. For a detailed description of all of them and what they mean,
|
||||
read the syntax reference on strings.
|
||||
|
||||
|
||||
.. hint::
|
||||
Strings and uwords (=memory address) can often be interchanged.
|
||||
An array of strings is actually an array of uwords where every element is the memory
|
||||
address of the string. You can pass a memory address to assembly functions
|
||||
@ -361,13 +373,6 @@ address you specified, and setting the varible will directly modify that memory
|
||||
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
|
||||
|
||||
|
||||
.. note::
|
||||
Directly accessing random memory locations is not yet supported without the
|
||||
intermediate step of declaring a memory-mapped variable for the memory location.
|
||||
The advantages of this however, is that it's clearer what the memory location
|
||||
stands for, and the compiler also knows the data type.
|
||||
|
||||
|
||||
Converting types into other types
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -645,23 +650,20 @@ There's a set of predefined functions in the language. These are fixed and can't
|
||||
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
|
||||
|
||||
|
||||
sin(x)
|
||||
Sine. (floating point version)
|
||||
Math
|
||||
^^^^
|
||||
|
||||
abs(x)
|
||||
Absolute value.
|
||||
|
||||
atan(x)
|
||||
Arctangent.
|
||||
|
||||
ceil(x)
|
||||
Rounds the floating point up to an integer towards positive infinity.
|
||||
|
||||
cos(x)
|
||||
Cosine. (floating point version)
|
||||
|
||||
sin8u(x)
|
||||
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
|
||||
|
||||
sin8(x)
|
||||
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
|
||||
|
||||
sin16u(x)
|
||||
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
|
||||
|
||||
sin16(x)
|
||||
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
|
||||
Cosine. (floating point version)
|
||||
|
||||
cos8u(x)
|
||||
Fast 8-bit ubyte cosine of angle 0..255, result is in range 0..255
|
||||
@ -675,14 +677,11 @@ cos16u(x)
|
||||
cos16(x)
|
||||
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
|
||||
|
||||
abs(x)
|
||||
Absolute value.
|
||||
deg(x)
|
||||
Radians to degrees.
|
||||
|
||||
tan(x)
|
||||
Tangent.
|
||||
|
||||
atan(x)
|
||||
Arctangent.
|
||||
floor (x)
|
||||
Rounds the floating point down to an integer towards minus infinity.
|
||||
|
||||
ln(x)
|
||||
Natural logarithm (base e).
|
||||
@ -690,45 +689,48 @@ ln(x)
|
||||
log2(x)
|
||||
Base 2 logarithm.
|
||||
|
||||
rad(x)
|
||||
Degrees to radians.
|
||||
|
||||
round(x)
|
||||
Rounds the floating point to the closest integer.
|
||||
|
||||
sin(x)
|
||||
Sine. (floating point version)
|
||||
|
||||
sgn(x)
|
||||
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
|
||||
|
||||
sin8u(x)
|
||||
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
|
||||
|
||||
sin8(x)
|
||||
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
|
||||
|
||||
sin16u(x)
|
||||
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
|
||||
|
||||
sin16(x)
|
||||
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
|
||||
|
||||
sqrt16(w)
|
||||
16 bit unsigned integer Square root. Result is unsigned byte.
|
||||
|
||||
sqrt(x)
|
||||
Floating point Square root.
|
||||
|
||||
round(x)
|
||||
Rounds the floating point to the closest integer.
|
||||
tan(x)
|
||||
Tangent.
|
||||
|
||||
floor (x)
|
||||
Rounds the floating point down to an integer towards minus infinity.
|
||||
|
||||
ceil(x)
|
||||
Rounds the floating point up to an integer towards positive infinity.
|
||||
Array operations
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
rad(x)
|
||||
Degrees to radians.
|
||||
any(x)
|
||||
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
|
||||
|
||||
deg(x)
|
||||
Radians to degrees.
|
||||
|
||||
max(x)
|
||||
Maximum of the values in the array value x
|
||||
|
||||
min(x)
|
||||
Minimum of the values in the array value x
|
||||
|
||||
sum(x)
|
||||
Sum of the values in the array value x
|
||||
|
||||
sort(array)
|
||||
Sort the array in ascending order (in-place)
|
||||
Note: sorting a floating-point array is not supported right now, as a general sorting routine for this will
|
||||
be extremely slow. Either build one yourself or find another solution that doesn't require sorting
|
||||
floating point values.
|
||||
|
||||
reverse(array)
|
||||
Reverse the values in the array (in-place).
|
||||
Can be used after sort() to sort an array in descending order.
|
||||
all(x)
|
||||
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
|
||||
|
||||
len(x)
|
||||
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
|
||||
@ -737,15 +739,80 @@ len(x)
|
||||
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)
|
||||
|
||||
sizeof(name)
|
||||
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
|
||||
the object. For instance, for a variable of type uword, the sizeof is 2.
|
||||
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
|
||||
Note: usually you will be interested in the number of elements in an array, use len() for that.
|
||||
max(x)
|
||||
Maximum of the values in the array value x
|
||||
|
||||
min(x)
|
||||
Minimum of the values in the array value x
|
||||
|
||||
reverse(array)
|
||||
Reverse the values in the array (in-place).
|
||||
Can be used after sort() to sort an array in descending order.
|
||||
|
||||
sum(x)
|
||||
Sum of the values in the array value x
|
||||
|
||||
sort(array)
|
||||
Sort the array in ascending order (in-place)
|
||||
Supported are arrays of bytes or word values.
|
||||
Sorting a floating-point array is not supported right now, as a general sorting routine for this will
|
||||
be extremely slow. Either build one yourself or find another solution that doesn't require sorting.
|
||||
Finally, note that sorting an array with strings in it will not do what you might think;
|
||||
it considers the array as just an array of integer words and sorts the string *pointers* accordingly.
|
||||
Sorting strings alphabetically has to be programmed yourself if you need it.
|
||||
|
||||
|
||||
Strings and memory blocks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
memcopy(from, to, numbytes)
|
||||
Efficiently copy a number of bytes (1 - 256) from a memory location to another.
|
||||
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
|
||||
Because this function imposes some overhead to handle the parameters,
|
||||
it is only faster if the number of bytes is larger than a certain threshold.
|
||||
Compare the generated code to see if it was beneficial or not.
|
||||
The most efficient will always be to write a specialized copy routine in assembly yourself!
|
||||
|
||||
memset(address, numbytes, bytevalue)
|
||||
Efficiently set a part of memory to the given (u)byte value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
Note that for clearing the character screen, very fast specialized subroutines are
|
||||
available in the ``txt`` block (part of the ``textio`` module)
|
||||
|
||||
memsetw(address, numwords, wordvalue)
|
||||
Efficiently set a part of memory to the given (u)word value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
|
||||
leftstr(source, target, length)
|
||||
Copies the left side of the source string of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
rightstr(source, target, length)
|
||||
Copies the right side of the source string of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
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.
|
||||
Don't confuse this with ``len`` and ``sizeof``
|
||||
|
||||
strcmp(string1, string2)
|
||||
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
|
||||
Note that you can also directly compare strings and string values with eachother
|
||||
using ``==``, ``<`` etcetera (it will use strcmp for you under water automatically).
|
||||
|
||||
substr(source, target, start, length)
|
||||
Copies a segment from the source string, starting at the given index,
|
||||
and of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
Miscellaneous
|
||||
^^^^^^^^^^^^^
|
||||
exit(returncode)
|
||||
Immediately stops the program and exits it, with the returncode in the A register.
|
||||
Note: custom interrupt handlers remain active unless manually cleared first!
|
||||
|
||||
lsb(x)
|
||||
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
|
||||
@ -753,19 +820,10 @@ lsb(x)
|
||||
msb(x)
|
||||
Get the most significant byte of the word x.
|
||||
|
||||
sgn(x)
|
||||
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
|
||||
|
||||
mkword(msb, lsb)
|
||||
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
|
||||
So mkword($80, $22) results in $8022.
|
||||
|
||||
any(x)
|
||||
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
|
||||
|
||||
all(x)
|
||||
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
|
||||
|
||||
rnd()
|
||||
returns a pseudo-random byte from 0..255
|
||||
|
||||
@ -799,51 +857,6 @@ ror2(x)
|
||||
It uses some extra logic to not consider the carry flag as extra rotation bit.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
memcopy(from, to, numbytes)
|
||||
Efficiently copy a number of bytes (1 - 256) from a memory location to another.
|
||||
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
|
||||
Because this function imposes some overhead to handle the parameters,
|
||||
it is only faster if the number of bytes is larger than a certain threshold.
|
||||
Compare the generated code to see if it was beneficial or not.
|
||||
The most efficient will always be to write a specialized copy routine in assembly yourself!
|
||||
|
||||
memset(address, numbytes, bytevalue)
|
||||
Efficiently set a part of memory to the given (u)byte value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
Note that for clearing the character screen, very fast specialized subroutines are
|
||||
available in the ``txt`` block (part of the ``textio`` module)
|
||||
|
||||
memsetw(address, numwords, wordvalue)
|
||||
Efficiently set a part of memory to the given (u)word value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
|
||||
leftstr(source, target, length)
|
||||
Copies the left side of the source string of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
rightstr(source, target, length)
|
||||
Copies the right side of the source string of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
substr(source, target, start, length)
|
||||
Copies a segment from the source string, starting at the given index,
|
||||
and of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
swap(x, y)
|
||||
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
||||
|
||||
set_carry() / clear_carry()
|
||||
Set (or clear) the CPU status register Carry flag. No result value.
|
||||
(translated into ``SEC`` or ``CLC`` cpu instruction)
|
||||
|
||||
set_irqd() / clear_irqd()
|
||||
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
|
||||
(translated into ``SEI`` or ``CLI`` cpu instruction)
|
||||
|
||||
rsave()
|
||||
Saves the CPU registers and the status flags.
|
||||
You can now more or less 'safely' use the registers directly, until you
|
||||
@ -858,10 +871,22 @@ rrestore()
|
||||
read_flags()
|
||||
Returns the current value of the CPU status register.
|
||||
|
||||
exit(returncode)
|
||||
Immediately stops the program and exits it, with the returncode in the A register.
|
||||
Note: custom interrupt handlers remain active unless manually cleared first!
|
||||
sizeof(name)
|
||||
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
|
||||
the object. For instance, for a variable of type uword, the sizeof is 2.
|
||||
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
|
||||
Note: usually you will be interested in the number of elements in an array, use len() for that.
|
||||
|
||||
set_carry() / clear_carry()
|
||||
Set (or clear) the CPU status register Carry flag. No result value.
|
||||
(translated into ``SEC`` or ``CLC`` cpu instruction)
|
||||
|
||||
set_irqd() / clear_irqd()
|
||||
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
|
||||
(translated into ``SEI`` or ``CLI`` cpu instruction)
|
||||
|
||||
swap(x, y)
|
||||
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
||||
|
||||
|
||||
Library routines
|
||||
|
@ -78,7 +78,7 @@ Directives
|
||||
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
|
||||
except the few addresses mentioned above that are used by the system's IRQ routine.
|
||||
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print``.
|
||||
This option makes programs smaller and faster because even more variables can
|
||||
be stored in the ZP (which allows for more efficient assembly code).
|
||||
It's not possible to return cleanly to BASIC when the program exits. The only choice is
|
||||
@ -402,6 +402,24 @@ Struct variables can be assigned a struct literal value (also in their declarati
|
||||
Color rgb = [255, 100, 0] ; note that the value is an array
|
||||
|
||||
|
||||
String
|
||||
^^^^^^
|
||||
|
||||
``"hello"`` is a string translated into the default character encoding (PETSCII)
|
||||
|
||||
``@"hello"`` is a string translated into the alternate character encoding (Screencodes/pokes)
|
||||
|
||||
There are several escape sequences available to put special characters into your string value:
|
||||
|
||||
- ``\\`` - the backslash itself, has to be escaped because it is the escape symbol by itself
|
||||
- ``\n`` - newline character (move cursor down and to beginning of next line)
|
||||
- ``\r`` - carriage return character (more or less the same as newline if printing to the screen)
|
||||
- ``\"`` - quote character (otherwise it would terminate the string)
|
||||
- ``\'`` - apostrophe character (has to be escaped in character literals, is okay inside a string)
|
||||
- ``\uHHHH`` - a unicode codepoint \u0000 - \uffff (16-bit hexadecimal)
|
||||
- ``\xHH`` - 8-bit hex value that will be copied verbatim *without encoding*
|
||||
|
||||
|
||||
Operators
|
||||
---------
|
||||
|
||||
@ -486,6 +504,8 @@ takes no parameters. If the subroutine returns a value, usually you assign it t
|
||||
If you're not interested in the return value, prefix the function call with the ``void`` keyword.
|
||||
Otherwise the compiler will warn you about discarding the result of the call.
|
||||
|
||||
Multiple return values
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
Normal subroutines can only return zero or one return values.
|
||||
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
|
||||
(referencing a routine in kernel ROM) can return more than one return value.
|
||||
@ -493,9 +513,16 @@ For example a status in the carry bit and a number in A, or a 16-bit value in A/
|
||||
It is not possible to process the results of a call to these kind of routines
|
||||
directly from the language, because only single value assignments are possible.
|
||||
You can still call the subroutine and not store the results.
|
||||
But if you want to do something with the values it returns, you'll have to write
|
||||
a small block of custom inline assembly that does the call and stores the values
|
||||
appropriately. Don't forget to save/restore the registers if required.
|
||||
|
||||
**There is an exception:** if there's just one return value in a register, and one or more others that are returned
|
||||
as bits in the status register (such as the Carry bit), the compiler allows you to call the subroutine.
|
||||
It will then store the result value in a variable if required, and *keep the status register untouched
|
||||
after the call* so you can use a conditional branch statement for that.
|
||||
Note that this makes no sense inside an expression, so the compiler will still give an error for that.
|
||||
|
||||
If there really are multiple return values (other than a combined 16 bit return value in 2 registers),
|
||||
you'll have to write a small block of custom inline assembly that does the call and stores the values
|
||||
appropriately. Don't forget to save/restore any registers that are modified.
|
||||
|
||||
|
||||
Subroutine definitions
|
||||
|
@ -16,8 +16,9 @@ Currently there are two machines that are supported as compiler target (selectab
|
||||
|
||||
This chapter explains the relevant system details of these machines.
|
||||
|
||||
.. note::
|
||||
If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
.. hint::
|
||||
If you only use standard kernel and prog8 library routines,
|
||||
it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
|
||||
|
||||
Memory Model
|
||||
|
@ -3,7 +3,6 @@ TODO
|
||||
====
|
||||
|
||||
- get rid of all other TODO's in the code ;-)
|
||||
- make it possible for array literals to not only contain compile time constants?
|
||||
- implement @stack for asmsub parameters
|
||||
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
|
||||
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
|
||||
@ -19,7 +18,7 @@ Add more compiler optimizations to the existing ones.
|
||||
|
||||
- further optimize assignment codegeneration, such as the following:
|
||||
- binexpr splitting (beware self-referencing expressions and asm code ballooning though)
|
||||
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
|
||||
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise. Especially for built-in functions!
|
||||
- can such parameter passing to subroutines be optimized to avoid copying?
|
||||
- more optimizations on the language AST level
|
||||
- more optimizations on the final assembly source level
|
||||
|
Binary file not shown.
Binary file not shown.
BIN
examples/compiled/textelite.prg
Normal file
BIN
examples/compiled/textelite.prg
Normal file
Binary file not shown.
@ -166,7 +166,7 @@ main {
|
||||
else
|
||||
c64.SPRPTR[i] = $2000/64 ; small ball
|
||||
|
||||
c64.SPCOL[i] = spritecolors[(zc>>13) as byte + 4] ; further away=darker color
|
||||
c64.SPCOL[i] = spritecolors[(zc>>13) as ubyte + 4] ; further away=darker color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,14 @@
|
||||
%target cx16
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
void cx16.screen_set_mode($80)
|
||||
cx16.r0=0
|
||||
void cx16.screen_set_mode(0)
|
||||
|
||||
cx16.FB_init()
|
||||
cx16.GRAPH_init()
|
||||
|
17
examples/cxlogo.p8
Normal file
17
examples/cxlogo.p8
Normal file
@ -0,0 +1,17 @@
|
||||
%import textio
|
||||
%import cx16logo
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
repeat {
|
||||
ubyte col = rnd() % (txt.DEFAULT_WIDTH-12) + 3
|
||||
ubyte row = rnd() % (txt.DEFAULT_HEIGHT-7)
|
||||
cx16logo.logo_at(col, row)
|
||||
txt.plot(col-3, row+7 )
|
||||
txt.print("commander x16")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
%target c64
|
||||
%import textio
|
||||
%import syslib
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address 50000
|
||||
|
||||
; This example shows the directory contents of disk drive 8.
|
||||
; You load it with LOAD "diskdir-sys50000",8,1
|
||||
; and then call it with SYS 50000.
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.print("directory of disk drive #8:\n\n")
|
||||
diskdir(8)
|
||||
}
|
||||
|
||||
sub diskdir(ubyte drivenumber) {
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
c64.OPEN() ; open 1,8,0,"$"
|
||||
c64.CHKIN(1) ; use #1 as input channel
|
||||
|
||||
repeat 4 {
|
||||
void c64.CHRIN() ; skip the 4 prologue bytes
|
||||
}
|
||||
|
||||
; while not key pressed / EOF encountered, read data.
|
||||
while not (@($c6) | c64.STATUS) {
|
||||
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
|
||||
txt.chrout(' ')
|
||||
ubyte @zp char
|
||||
do {
|
||||
char = c64.CHRIN()
|
||||
txt.chrout(char)
|
||||
} until char==0
|
||||
txt.chrout('\n')
|
||||
repeat 2 {
|
||||
void c64.CHRIN() ; skip 2 bytes
|
||||
}
|
||||
}
|
||||
|
||||
c64.CLOSE(1)
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
%target c64
|
||||
%import textio
|
||||
%import syslib
|
||||
%option no_sysinit
|
||||
%zeropage basicsafe
|
||||
|
||||
; This example shows the directory contents of disk drive 8.
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
txt.print("directory of disk drive #8:\n\n")
|
||||
diskdir(8)
|
||||
}
|
||||
|
||||
sub diskdir(ubyte drivenumber) {
|
||||
c64.SETNAM(1, "$")
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
c64.OPEN() ; open 1,8,0,"$"
|
||||
c64.CHKIN(1) ; use #1 as input channel
|
||||
|
||||
repeat 4 {
|
||||
void c64.CHRIN() ; skip the 4 prologue bytes
|
||||
}
|
||||
|
||||
; while not key pressed / EOF encountered, read data.
|
||||
while not (@($c6) | c64.STATUS) {
|
||||
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
|
||||
txt.chrout(' ')
|
||||
ubyte @zp char
|
||||
do {
|
||||
char = c64.CHRIN()
|
||||
txt.chrout(char)
|
||||
} until char==0
|
||||
txt.chrout('\n')
|
||||
repeat 2 {
|
||||
void c64.CHRIN() ; skip 2 bytes
|
||||
}
|
||||
}
|
||||
|
||||
c64.CLOSE(1)
|
||||
c64.CLRCHN() ; restore default i/o devices
|
||||
}
|
||||
}
|
@ -9,14 +9,14 @@
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
const ubyte width = 255
|
||||
const uword width = 320
|
||||
const ubyte height = 200
|
||||
const ubyte max_iter = 16
|
||||
|
||||
sub start() {
|
||||
graphics.enable_bitmap_mode()
|
||||
|
||||
ubyte pixelx
|
||||
uword pixelx
|
||||
ubyte pixely
|
||||
|
||||
for pixely in 0 to height-1 {
|
||||
|
@ -30,7 +30,7 @@ main {
|
||||
txt.print("es")
|
||||
txt.print(" left.\nWhat is your next guess? ")
|
||||
void txt.input_chars(input)
|
||||
ubyte guess = lsb(conv.str2uword(input))
|
||||
ubyte guess = conv.str2ubyte(input)
|
||||
|
||||
if guess==secretnumber {
|
||||
ending(true)
|
||||
|
@ -218,7 +218,7 @@ waitkey:
|
||||
if linepos and blocklogic.isLineFull(linepos)
|
||||
blocklogic.collapse(linepos)
|
||||
lines += num_lines
|
||||
uword[] scores = [10, 25, 50, 100] ; can never clear more than 4 lines
|
||||
uword[] scores = [10, 25, 50, 100] ; can never clear more than 4 lines at once
|
||||
score += scores[num_lines-1]
|
||||
speedlevel = 1+lsb(lines/10)
|
||||
drawScore()
|
||||
@ -249,8 +249,10 @@ waitkey:
|
||||
txt.print("────────────────────────")
|
||||
c64.CHROUT('K')
|
||||
|
||||
while c64.GETIN()!=133 {
|
||||
ubyte key = 0
|
||||
while key!=133 {
|
||||
; endless loop until user presses F1 to restart the game
|
||||
key = c64.GETIN()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,41 +1,23 @@
|
||||
%import textio
|
||||
%import syslib
|
||||
%import conv
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
|
||||
struct Color {
|
||||
ubyte red
|
||||
ubyte green
|
||||
ubyte blue
|
||||
}
|
||||
|
||||
Color c1 = [11,22,33]
|
||||
Color c2 = [11,22,33]
|
||||
Color c3 = [11,22,33]
|
||||
uword[] colors = [ c1, c2, c3]
|
||||
|
||||
|
||||
sub start() {
|
||||
|
||||
txt.print_ub(c1.red)
|
||||
txt.chrout('\n')
|
||||
txt.print_ub(c1.green)
|
||||
txt.chrout('\n')
|
||||
txt.print_ub(c1.blue)
|
||||
txt.chrout('\n')
|
||||
txt.chrout('\n')
|
||||
uword[] array = [1, 2, 3]
|
||||
ubyte ii = 0
|
||||
ubyte ii2 = ii+2
|
||||
array[ii+1] = array[ii2] ; TODO fix overwriting the single array index autovar
|
||||
|
||||
c1 = [99,88,77]
|
||||
uword xx
|
||||
for xx in array {
|
||||
txt.print_uw(xx)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
txt.print_ub(c1.red)
|
||||
txt.chrout('\n')
|
||||
txt.print_ub(c1.green)
|
||||
txt.chrout('\n')
|
||||
txt.print_ub(c1.blue)
|
||||
txt.chrout('\n')
|
||||
testX()
|
||||
}
|
||||
|
||||
@ -53,5 +35,4 @@ main {
|
||||
_saveX .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ main {
|
||||
|
||||
&str ms1 = $c000
|
||||
|
||||
|
||||
byte[4] barray
|
||||
ubyte[4] ubarray
|
||||
word[4] warray
|
||||
@ -84,20 +83,20 @@ main {
|
||||
uw=muwarray[bb]
|
||||
fl=mflarray[bb]
|
||||
|
||||
A=s1[bb*3]
|
||||
ub=s1[bb*3]
|
||||
bb=barray[bb*3]
|
||||
ub=ubarray[bb*3]
|
||||
ww=warray[bb*3]
|
||||
uw=uwarray[bb*3]
|
||||
fl=flarray[bb*3]
|
||||
A=ms1[bb*3]
|
||||
ub=ms1[bb*3]
|
||||
bb=mbarray[bb*3]
|
||||
ub=mubarray[bb*3]
|
||||
ww=mwarray[bb*3]
|
||||
uw=muwarray[bb*3]
|
||||
fl=mflarray[bb*3]
|
||||
; A=s1[bb*3]
|
||||
; ub=s1[bb*3]
|
||||
; bb=barray[bb*3]
|
||||
; ub=ubarray[bb*3]
|
||||
; ww=warray[bb*3]
|
||||
; uw=uwarray[bb*3]
|
||||
; fl=flarray[bb*3]
|
||||
; A=ms1[bb*3]
|
||||
; ub=ms1[bb*3]
|
||||
; bb=mbarray[bb*3]
|
||||
; ub=mubarray[bb*3]
|
||||
; ww=mwarray[bb*3]
|
||||
; uw=muwarray[bb*3]
|
||||
; fl=mflarray[bb*3]
|
||||
|
||||
; write array
|
||||
barray[2]++
|
||||
@ -133,11 +132,11 @@ main {
|
||||
uwarray[bb] = uw
|
||||
flarray[bb] = fl
|
||||
|
||||
s1[bb*3] = ub
|
||||
barray[bb*3] = bb
|
||||
ubarray[bb*3] = ub
|
||||
warray[bb*3] = ww
|
||||
uwarray[bb*3] = uw
|
||||
flarray[bb*3] = fl
|
||||
; s1[bb*3] = ub
|
||||
; barray[bb*3] = bb
|
||||
; ubarray[bb*3] = ub
|
||||
; warray[bb*3] = ww
|
||||
; uwarray[bb*3] = uw
|
||||
; flarray[bb*3] = fl
|
||||
}
|
||||
}
|
||||
|
999
examples/textelite.p8
Normal file
999
examples/textelite.p8
Normal file
@ -0,0 +1,999 @@
|
||||
%import textio
|
||||
%import conv
|
||||
%import diskio
|
||||
%option no_sysinit
|
||||
%zeropage basicsafe
|
||||
|
||||
; Prog8 adaptation of the Text-Elite galaxy system trading simulation engine.
|
||||
; Original C-version obtained from: http://www.elitehomepage.org/text/index.htm
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
const ubyte numforLave = 7 ; Lave is 7th generated planet in galaxy one
|
||||
const ubyte numforZaonce = 129
|
||||
const ubyte numforDiso = 147
|
||||
const ubyte numforRiedquat = 46
|
||||
|
||||
sub start() {
|
||||
txt.lowercase()
|
||||
txt.print("\u000c\n --- TextElite v1.1 ---\n")
|
||||
|
||||
galaxy.travel_to(1, numforLave)
|
||||
market.init(0) ; Lave's market is seeded with 0
|
||||
ship.init()
|
||||
planet.display(false)
|
||||
|
||||
repeat {
|
||||
str input = "????????"
|
||||
txt.print("\nCash: ")
|
||||
util.print_10s(ship.cash)
|
||||
txt.print("\nCommand (?=help): ")
|
||||
ubyte num_chars = txt.input_chars(input)
|
||||
txt.chrout('\n')
|
||||
if num_chars {
|
||||
when input[0] {
|
||||
'?' -> {
|
||||
txt.print("\nCommands are:\n"+
|
||||
"buy jump info cash >=save\n"+
|
||||
"sell teleport market hold <=load\n"+
|
||||
"fuel galhyp local quit\n")
|
||||
}
|
||||
'q' -> break
|
||||
'b' -> trader.do_buy()
|
||||
's' -> trader.do_sell()
|
||||
'f' -> trader.do_fuel()
|
||||
'j' -> trader.do_jump()
|
||||
't' -> trader.do_teleport()
|
||||
'g' -> trader.do_next_galaxy()
|
||||
'i' -> trader.do_info()
|
||||
'm' -> trader.do_show_market()
|
||||
'l' -> trader.do_local()
|
||||
'c' -> trader.do_cash()
|
||||
'h' -> trader.do_hold()
|
||||
'<' -> trader.do_load()
|
||||
'>' -> trader.do_save()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trader {
|
||||
str Savegame = "↑commander.save"
|
||||
str input = "??????????"
|
||||
ubyte num_chars
|
||||
|
||||
struct SaveData {
|
||||
ubyte galaxy
|
||||
ubyte planet
|
||||
ubyte cargo0
|
||||
ubyte cargo1
|
||||
ubyte cargo2
|
||||
ubyte cargo3
|
||||
ubyte cargo4
|
||||
ubyte cargo5
|
||||
ubyte cargo6
|
||||
ubyte cargo7
|
||||
ubyte cargo8
|
||||
ubyte cargo9
|
||||
ubyte cargo10
|
||||
ubyte cargo11
|
||||
ubyte cargo12
|
||||
ubyte cargo13
|
||||
ubyte cargo14
|
||||
ubyte cargo15
|
||||
ubyte cargo16
|
||||
uword cash
|
||||
ubyte max_cargo
|
||||
ubyte fuel
|
||||
}
|
||||
SaveData savedata
|
||||
|
||||
sub do_load() {
|
||||
txt.print("\nLoading universe...")
|
||||
if diskio.load(8, Savegame, &savedata) {
|
||||
txt.print("ok\n")
|
||||
} else {
|
||||
txt.print("\ni/o error: ")
|
||||
diskio.status(8)
|
||||
txt.chrout('\n')
|
||||
return
|
||||
}
|
||||
|
||||
ship.cash = savedata.cash
|
||||
ship.Max_cargo = savedata.max_cargo
|
||||
ship.fuel = savedata.fuel
|
||||
memcopy(&savedata.cargo0, ship.cargohold, len(ship.cargohold))
|
||||
galaxy.travel_to(savedata.galaxy, savedata.planet)
|
||||
|
||||
planet.display(false)
|
||||
}
|
||||
|
||||
sub do_save() {
|
||||
savedata.galaxy = galaxy.number
|
||||
savedata.planet = planet.number
|
||||
savedata.cash = ship.cash
|
||||
savedata.max_cargo = ship.Max_cargo
|
||||
savedata.fuel = ship.fuel
|
||||
memcopy(ship.cargohold, &savedata.cargo0, len(ship.cargohold))
|
||||
|
||||
txt.print("\nSaving universe...")
|
||||
diskio.delete(8, Savegame)
|
||||
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
|
||||
txt.print("ok\n")
|
||||
} else {
|
||||
txt.print("\ni/o error: ")
|
||||
diskio.status(8)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
}
|
||||
|
||||
sub do_jump() {
|
||||
txt.print("\nJump to what system? ")
|
||||
jump_to_system()
|
||||
}
|
||||
|
||||
sub do_teleport() {
|
||||
txt.print("\nCheat! Teleport to what system? ")
|
||||
ubyte fuel = ship.fuel
|
||||
ship.fuel = 255
|
||||
jump_to_system()
|
||||
ship.fuel = fuel
|
||||
}
|
||||
|
||||
sub jump_to_system() {
|
||||
void txt.input_chars(input)
|
||||
ubyte current_planet = planet.number
|
||||
ubyte x = planet.x
|
||||
ubyte y = planet.y
|
||||
if galaxy.search_closest_planet(input) {
|
||||
ubyte distance = planet.distance(x, y)
|
||||
if distance <= ship.fuel {
|
||||
galaxy.init_market_for_planet()
|
||||
ship.fuel -= distance
|
||||
txt.print("\n\nHyperspace jump! Arrived at:\n")
|
||||
planet.display(true)
|
||||
return
|
||||
}
|
||||
txt.print("Insufficient fuel\n")
|
||||
} else {
|
||||
txt.print(" Not found!\n")
|
||||
}
|
||||
galaxy.travel_to(galaxy.number, current_planet)
|
||||
}
|
||||
|
||||
sub do_buy() {
|
||||
txt.print("\nBuy what commodity? ")
|
||||
str commodity = "???????????????"
|
||||
void txt.input_chars(commodity)
|
||||
ubyte ci = market.match(commodity)
|
||||
if ci & 128 {
|
||||
txt.print("Unknown\n")
|
||||
} else {
|
||||
txt.print("\nHow much? ")
|
||||
void txt.input_chars(input)
|
||||
ubyte amount = conv.str2ubyte(input)
|
||||
if market.current_quantity[ci] < amount {
|
||||
txt.print(" Insufficient supply!\n")
|
||||
} else {
|
||||
uword price = market.current_price[ci] * amount
|
||||
txt.print(" Total price: ")
|
||||
util.print_10s(price)
|
||||
if price > ship.cash {
|
||||
txt.print(" Not enough cash!\n")
|
||||
} else {
|
||||
ship.cash -= price
|
||||
ship.cargohold[ci] += amount
|
||||
market.current_quantity[ci] -= amount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub do_sell() {
|
||||
txt.print("\nSell what commodity? ")
|
||||
str commodity = "???????????????"
|
||||
void txt.input_chars(commodity)
|
||||
ubyte ci = market.match(commodity)
|
||||
if ci & 128 {
|
||||
txt.print("Unknown\n")
|
||||
} else {
|
||||
txt.print("\nHow much? ")
|
||||
void txt.input_chars(input)
|
||||
ubyte amount = conv.str2ubyte(input)
|
||||
if ship.cargohold[ci] < amount {
|
||||
txt.print(" Insufficient supply!\n")
|
||||
} else {
|
||||
uword price = market.current_price[ci] * amount
|
||||
txt.print(" Total price: ")
|
||||
util.print_10s(price)
|
||||
ship.cash += price
|
||||
ship.cargohold[ci] -= amount
|
||||
market.current_quantity[ci] += amount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub do_fuel() {
|
||||
txt.print("\nBuy fuel. Amount? ")
|
||||
void txt.input_chars(input)
|
||||
ubyte buy_fuel = 10*conv.str2ubyte(input)
|
||||
ubyte max_fuel = ship.Max_fuel - ship.fuel
|
||||
if buy_fuel > max_fuel
|
||||
buy_fuel = max_fuel
|
||||
uword price = buy_fuel as uword * ship.Fuel_cost
|
||||
if price > ship.cash {
|
||||
txt.print("Not enough cash!\n")
|
||||
} else {
|
||||
ship.cash -= price
|
||||
ship.fuel += buy_fuel
|
||||
}
|
||||
}
|
||||
|
||||
sub do_cash() {
|
||||
txt.print("\nCheat! Set cash amount: ")
|
||||
void txt.input_chars(input)
|
||||
ship.cash = conv.str2uword(input)
|
||||
}
|
||||
|
||||
sub do_hold() {
|
||||
txt.print("\nCheat! Set cargohold size: ")
|
||||
void txt.input_chars(input)
|
||||
ship.Max_cargo = conv.str2ubyte(input)
|
||||
}
|
||||
|
||||
sub do_next_galaxy() {
|
||||
galaxy.travel_to(galaxy.number+1, planet.number)
|
||||
planet.display(false)
|
||||
}
|
||||
|
||||
sub do_info() {
|
||||
txt.print("\nSystem name (empty=current): ")
|
||||
num_chars = txt.input_chars(input)
|
||||
if num_chars {
|
||||
ubyte current_planet = planet.number
|
||||
if galaxy.search_closest_planet(input) {
|
||||
planet.display(false)
|
||||
} else {
|
||||
txt.print(" Not found!")
|
||||
}
|
||||
galaxy.travel_to(galaxy.number, current_planet)
|
||||
} else {
|
||||
planet.display(false)
|
||||
}
|
||||
}
|
||||
|
||||
sub do_local() {
|
||||
galaxy.local_area()
|
||||
}
|
||||
|
||||
sub do_show_market() {
|
||||
market.display()
|
||||
txt.print("\nFuel: ")
|
||||
util.print_10s(ship.fuel)
|
||||
txt.print(" Cargohold space: ")
|
||||
txt.print_ub(ship.cargo_free())
|
||||
txt.print("t\n")
|
||||
}
|
||||
}
|
||||
|
||||
ship {
|
||||
const ubyte Max_fuel = 70
|
||||
const ubyte Fuel_cost = 2
|
||||
ubyte Max_cargo = 20
|
||||
|
||||
ubyte fuel = Max_fuel
|
||||
uword cash = 1000 ; actually has to be 4 bytes for the ultra rich....
|
||||
ubyte[17] cargohold = 0
|
||||
|
||||
sub init() {
|
||||
memset(cargohold, len(cargohold), 0)
|
||||
}
|
||||
|
||||
sub cargo_free() -> ubyte {
|
||||
ubyte ci
|
||||
ubyte total = 0
|
||||
for ci in 0 to len(cargohold)-1 {
|
||||
if market.units[ci]==0 ; tonnes only
|
||||
total += cargohold[ci]
|
||||
}
|
||||
return Max_cargo - total
|
||||
}
|
||||
}
|
||||
|
||||
market {
|
||||
ubyte[17] baseprices = [$13, $14, $41, $28, $53, $C4, $EB, $9A, $75, $4E, $7C, $B0, $20, $61, $AB, $2D, $35]
|
||||
byte[17] gradients = [-$02, -$01, -$03, -$05, -$05, $08, $1D, $0E, $06, $01, $0d, -$09, -$01, -$01, -$02, -$01, $0F]
|
||||
ubyte[17] basequants = [$06, $0A, $02, $E2, $FB, $36, $08, $38, $28, $11, $1D, $DC, $35, $42, $37, $FA, $C0]
|
||||
ubyte[17] maskbytes = [$01, $03, $07, $1F, $0F, $03, $78, $03, $07, $1F, $07, $3F, $03, $07, $1F, $0F, $07]
|
||||
ubyte[17] units = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 0]
|
||||
str[17] names = ["Food", "Textiles", "Radioactives", "Slaves", "Liquor/Wines", "Luxuries", "Narcotics", "Computers",
|
||||
"Machinery", "Alloys", "Firearms", "Furs", "Minerals", "Gold", "Platinum", "Gem-Stones", "Alien Items"]
|
||||
|
||||
ubyte[17] current_quantity = 0
|
||||
uword[17] current_price = 0
|
||||
|
||||
sub init(ubyte fluct) {
|
||||
; Prices and availabilities are influenced by the planet's economy type
|
||||
; (0-7) and a random "fluctuation" byte that was kept within the saved
|
||||
; commander position to keep the market prices constant over gamesaves.
|
||||
; Availabilities must be saved with the game since the player alters them
|
||||
; by buying (and selling(?))
|
||||
;
|
||||
; Almost all operations are one byte only and overflow "errors" are
|
||||
; extremely frequent and exploited.
|
||||
;
|
||||
; Trade Item prices are held internally in a single byte=true value/4.
|
||||
; The decimal point in prices is introduced only when printing them.
|
||||
; Internally, all prices are integers.
|
||||
; The player's cash is held in four bytes.
|
||||
ubyte ci
|
||||
for ci in 0 to len(names)-1 {
|
||||
word product
|
||||
byte changing
|
||||
product = planet.economy as word * gradients[ci]
|
||||
changing = fluct & maskbytes[ci] as byte
|
||||
ubyte q = (basequants[ci] as word + changing - product) as ubyte
|
||||
if q & $80
|
||||
q = 0 ; clip to positive 8-bit
|
||||
current_quantity[ci] = q & $3f
|
||||
q = (baseprices[ci] + changing + product) as ubyte
|
||||
current_price[ci] = q * $0004
|
||||
}
|
||||
current_quantity[16] = 0 ; force nonavailability of Alien Items
|
||||
}
|
||||
|
||||
sub display() {
|
||||
ubyte ci
|
||||
txt.chrout('\n')
|
||||
planet.print_name_uppercase()
|
||||
txt.print(" trade market:\n COMMODITY / PRICE / AVAIL / IN HOLD\n")
|
||||
for ci in 0 to len(names)-1 {
|
||||
util.print_right(13, names[ci])
|
||||
txt.print(" ")
|
||||
util.print_10s(current_price[ci])
|
||||
txt.print(" ")
|
||||
txt.print_ub(current_quantity[ci])
|
||||
when units[ci] {
|
||||
0 -> txt.chrout('t')
|
||||
1 -> txt.print("kg")
|
||||
2 -> txt.chrout('g')
|
||||
}
|
||||
txt.print(" ")
|
||||
txt.print_ub(ship.cargohold[ci])
|
||||
txt.chrout('\n')
|
||||
}
|
||||
}
|
||||
|
||||
sub match(uword nameptr) -> ubyte {
|
||||
ubyte ci
|
||||
for ci in 0 to len(names)-1 {
|
||||
if util.prefix_matches(nameptr, names[ci])
|
||||
return ci
|
||||
}
|
||||
return 255
|
||||
}
|
||||
}
|
||||
|
||||
galaxy {
|
||||
const uword GALSIZE = 256
|
||||
const uword base0 = $5A4A ; seeds for the first galaxy
|
||||
const uword base1 = $0248
|
||||
const uword base2 = $B753
|
||||
|
||||
str pn_pairs = "..lexegezacebisousesarmaindirea.eratenberalavetiedorquanteisrion"
|
||||
|
||||
ubyte number
|
||||
|
||||
uword[3] seed
|
||||
|
||||
sub init(ubyte galaxynum) {
|
||||
number = 1
|
||||
planet.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
repeat galaxynum-1 {
|
||||
nextgalaxy()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
}
|
||||
|
||||
sub travel_to(ubyte galaxynum, ubyte system) {
|
||||
init(galaxynum)
|
||||
generate_next_planet() ; always at least planet 0 (separate to avoid repeat ubyte overflow)
|
||||
repeat system {
|
||||
generate_next_planet()
|
||||
}
|
||||
planet.name = make_current_planet_name()
|
||||
init_market_for_planet()
|
||||
}
|
||||
|
||||
sub init_market_for_planet() {
|
||||
market.init(lsb(seed[0])+msb(seed[2]))
|
||||
}
|
||||
|
||||
sub search_closest_planet(uword nameptr) -> ubyte {
|
||||
ubyte x = planet.x
|
||||
ubyte y = planet.y
|
||||
ubyte current_planet_num = planet.number
|
||||
|
||||
init(number)
|
||||
ubyte found = false
|
||||
ubyte current_closest_pi
|
||||
ubyte current_distance = 127
|
||||
ubyte pi
|
||||
for pi in 0 to 255 {
|
||||
generate_next_planet()
|
||||
planet.name = make_current_planet_name()
|
||||
if util.prefix_matches(nameptr, planet.name) {
|
||||
ubyte distance = planet.distance(x, y)
|
||||
if distance < current_distance {
|
||||
current_distance = distance
|
||||
current_closest_pi = pi
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if found
|
||||
travel_to(number, current_closest_pi)
|
||||
else
|
||||
travel_to(number, current_planet_num)
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
sub local_area() {
|
||||
ubyte current_planet = planet.number
|
||||
ubyte px = planet.x
|
||||
ubyte py = planet.y
|
||||
ubyte pn = 0
|
||||
|
||||
init(number)
|
||||
txt.print("\nGalaxy #")
|
||||
txt.print_ub(number)
|
||||
txt.print(" - systems in vicinity:\n")
|
||||
do {
|
||||
generate_next_planet()
|
||||
ubyte distance = planet.distance(px, py)
|
||||
if distance <= ship.Max_fuel {
|
||||
if distance <= ship.fuel
|
||||
txt.chrout('*')
|
||||
else
|
||||
txt.chrout('-')
|
||||
txt.chrout(' ')
|
||||
planet.name = make_current_planet_name()
|
||||
planet.display(true)
|
||||
txt.print(" (")
|
||||
util.print_10s(distance)
|
||||
txt.print(" LY)\n")
|
||||
}
|
||||
pn++
|
||||
} until pn==0
|
||||
|
||||
travel_to(number, current_planet)
|
||||
}
|
||||
|
||||
ubyte pn_pair1
|
||||
ubyte pn_pair2
|
||||
ubyte pn_pair3
|
||||
ubyte pn_pair4
|
||||
ubyte longname
|
||||
|
||||
sub generate_next_planet() {
|
||||
determine_planet_properties()
|
||||
longname = lsb(seed[0]) & 64
|
||||
|
||||
; Always four iterations of random number
|
||||
pn_pair1 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
pn_pair2 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
pn_pair3 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
pn_pair4 = (msb(seed[2]) & 31) * 2
|
||||
tweakseed()
|
||||
}
|
||||
|
||||
sub make_current_planet_name() -> str {
|
||||
ubyte ni = 0
|
||||
str name = " " ; max 8
|
||||
|
||||
if pn_pairs[pn_pair1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair1]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair1+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair1+1]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair2] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair2]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair2+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair2+1]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair3] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair3]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair3+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair3+1]
|
||||
ni++
|
||||
}
|
||||
|
||||
if longname {
|
||||
if pn_pairs[pn_pair4] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair4]
|
||||
ni++
|
||||
}
|
||||
if pn_pairs[pn_pair4+1] != '.' {
|
||||
name[ni] = pn_pairs[pn_pair4+1]
|
||||
ni++
|
||||
}
|
||||
}
|
||||
|
||||
name[ni] = 0
|
||||
return name
|
||||
}
|
||||
|
||||
sub determine_planet_properties() {
|
||||
; create the planet's characteristics
|
||||
planet.number++
|
||||
planet.x = msb(seed[1])
|
||||
planet.y = msb(seed[0])
|
||||
planet.govtype = lsb(seed[1]) >> 3 & 7 ; bits 3,4 &5 of w1
|
||||
planet.economy = msb(seed[0]) & 7 ; bits 8,9 &A of w0
|
||||
if planet.govtype <= 1
|
||||
planet.economy = (planet.economy | 2)
|
||||
planet.techlevel = (msb(seed[1]) & 3) + (planet.economy ^ 7)
|
||||
planet.techlevel += planet.govtype >> 1
|
||||
if planet.govtype & 1
|
||||
planet.techlevel++
|
||||
planet.population = 4 * planet.techlevel + planet.economy
|
||||
planet.population += planet.govtype + 1
|
||||
planet.productivity = ((planet.economy ^ 7) + 3) * (planet.govtype + 4)
|
||||
planet.productivity *= planet.population * 8
|
||||
ubyte seed2_msb = msb(seed[2])
|
||||
planet.radius = mkword((seed2_msb & 15) + 11, planet.x)
|
||||
planet.species_is_alien = lsb(seed[2]) & 128 ; bit 7 of w2_lo
|
||||
if planet.species_is_alien {
|
||||
planet.species_size = (seed2_msb >> 2) & 7 ; bits 2-4 of w2_hi
|
||||
planet.species_color = seed2_msb >> 5 ; bits 5-7 of w2_hi
|
||||
planet.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
|
||||
planet.species_kind = (planet.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
||||
uword temp = seed[0] + seed[1] + seed[2]
|
||||
seed[0] = seed[1]
|
||||
seed[1] = seed[2]
|
||||
seed[2] = temp
|
||||
}
|
||||
|
||||
sub twist(uword x) -> uword {
|
||||
ubyte xh = msb(x)
|
||||
ubyte xl = lsb(x)
|
||||
rol(xh)
|
||||
rol(xl)
|
||||
return mkword(xh, xl)
|
||||
}
|
||||
|
||||
sub debug_seed() {
|
||||
txt.print("\ngalaxy #")
|
||||
txt.print_ub(number)
|
||||
txt.print("\ngalaxy seed0=")
|
||||
txt.print_uwhex(galaxy.seed[0], true)
|
||||
txt.print("\ngalaxy seed1=")
|
||||
txt.print_uwhex(galaxy.seed[1], true)
|
||||
txt.print("\ngalaxy seed2=")
|
||||
txt.print_uwhex(galaxy.seed[2], true)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
}
|
||||
|
||||
planet {
|
||||
%option force_output
|
||||
|
||||
str[] species_sizes = ["Large", "Fierce", "Small"]
|
||||
str[] species_colors = ["Green", "Red", "Yellow", "Blue", "Black", "Harmless"]
|
||||
str[] species_looks = ["Slimy", "Bug-Eyed", "Horned", "Bony", "Fat", "Furry"]
|
||||
str[] species_kinds = ["Rodents", "Frogs", "Lizards", "Lobsters", "Birds", "Humanoids", "Felines", "Insects"]
|
||||
str[] govnames = ["Anarchy", "Feudal", "Multi-gov", "Dictatorship", "Communist", "Confederacy", "Democracy", "Corporate State"]
|
||||
str[] econnames = ["Rich Industrial", "Average Industrial", "Poor Industrial", "Mainly Industrial",
|
||||
"Mainly Agricultural", "Rich Agricultural", "Average Agricultural", "Poor Agricultural"]
|
||||
|
||||
str[] words81 = ["fabled", "notable", "well known", "famous", "noted"]
|
||||
str[] words82 = ["very", "mildly", "most", "reasonably", ""]
|
||||
str[] words83 = ["ancient", "\x95", "great", "vast", "pink"]
|
||||
str[] words84 = ["\x9E \x9D plantations", "mountains", "\x9C", "\x94 forests", "oceans"]
|
||||
str[] words85 = ["shyness", "silliness", "mating traditions", "loathing of \x86", "love for \x86"]
|
||||
str[] words86 = ["food blenders", "tourists", "poetry", "discos", "\x8E"]
|
||||
str[] words87 = ["talking tree", "crab", "bat", "lobst", "\xB2"]
|
||||
str[] words88 = ["beset", "plagued", "ravaged", "cursed", "scourged"]
|
||||
str[] words89 = ["\x96 civil war", "\x9B \x98 \x99s", "a \x9B disease", "\x96 earthquakes", "\x96 solar activity"]
|
||||
str[] words8A = ["its \x83 \x84", "the \xB1 \x98 \x99", "its inhabitants' \x9A \x85", "\xA1", "its \x8D \x8E"]
|
||||
str[] words8B = ["juice", "brandy", "water", "brew", "gargle blasters"]
|
||||
str[] words8C = ["\xB2", "\xB1 \x99", "\xB1 \xB2", "\xB1 \x9B", "\x9B \xB2"]
|
||||
str[] words8D = ["fabulous", "exotic", "hoopy", "unusual", "exciting"]
|
||||
str[] words8E = ["cuisine", "night life", "casinos", "sit coms", " \xA1 "]
|
||||
str[] words8F = ["\xB0", "The planet \xB0", "The world \xB0", "This planet", "This world"]
|
||||
str[] words90 = ["n unremarkable", " boring", " dull", " tedious", " revolting"]
|
||||
str[] words91 = ["planet", "world", "place", "little planet", "dump"]
|
||||
str[] words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
|
||||
str[] words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
|
||||
str[] words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
|
||||
str[] words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
|
||||
str[] words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
|
||||
str[] words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
|
||||
str[] words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
|
||||
str[] words99 = ["\x9F", "\xA0", "\x87oid", "\x93", "\x92"]
|
||||
str[] words9A = ["ancient", "exceptional", "eccentric", "ingrained", "\x95"]
|
||||
str[] words9B = ["killer", "deadly", "evil", "lethal", "vicious"]
|
||||
str[] words9C = ["parking meters", "dust clouds", "ice bergs", "rock formations", "volcanoes"]
|
||||
str[] words9D = ["plant", "tulip", "banana", "corn", "\xB2weed"]
|
||||
str[] words9E = ["\xB2", "\xB1 \xB2", "\xB1 \x9B", "inhabitant", "\xB1 \xB2"]
|
||||
str[] words9F = ["shrew", "beast", "bison", "snake", "wolf"]
|
||||
str[] wordsA0 = ["leopard", "cat", "monkey", "goat", "fish"]
|
||||
str[] wordsA1 = ["\x8C \x8B", "\xB1 \x9F \xA2", "its \x8D \xA0 \xA2", "\xA3 \xA4", "\x8C \x8B"]
|
||||
str[] wordsA2 = ["meat", "cutlet", "steak", "burgers", "soup"]
|
||||
str[] wordsA3 = ["ice", "mud", "Zero-G", "vacuum", "\xB1 ultra"]
|
||||
str[] wordsA4 = ["hockey", "cricket", "karate", "polo", "tennis"]
|
||||
|
||||
uword[] wordlists = [
|
||||
words81, words82, words83, words84, words85, words86, words87, words88,
|
||||
words89, words8A, words8B, words8C, words8D, words8E, words8F, words90,
|
||||
words91, words92, words93, words94, words95, words96, words97, words98,
|
||||
words99, words9A, words9B, words9C, words9D, words9E, words9F, wordsA0,
|
||||
wordsA1, wordsA2, wordsA3, wordsA4]
|
||||
|
||||
str pairs0 = "abouseitiletstonlonuthnoallexegezacebisousesarmaindirea.eratenbe"
|
||||
|
||||
ubyte[4] goatsoup_rnd = [0, 0, 0, 0]
|
||||
ubyte[4] goatsoup_seed = [0, 0, 0, 0]
|
||||
|
||||
str name = " " ; 8 max
|
||||
ubyte number ; starts at 0 in new galaxy, then increases by 1 for each generated planet
|
||||
ubyte x
|
||||
ubyte y
|
||||
ubyte economy
|
||||
ubyte govtype
|
||||
ubyte techlevel
|
||||
ubyte population
|
||||
uword productivity
|
||||
uword radius
|
||||
ubyte species_is_alien ; otherwise "Human Colonials"
|
||||
ubyte species_size
|
||||
ubyte species_color
|
||||
ubyte species_look
|
||||
ubyte species_kind
|
||||
|
||||
sub set_seed(uword s1, uword s2) {
|
||||
goatsoup_seed[0] = lsb(s1)
|
||||
goatsoup_seed[1] = msb(s1)
|
||||
goatsoup_seed[2] = lsb(s2)
|
||||
goatsoup_seed[3] = msb(s2)
|
||||
reset_rnd()
|
||||
}
|
||||
|
||||
sub reset_rnd() {
|
||||
goatsoup_rnd[0] = goatsoup_seed[0]
|
||||
goatsoup_rnd[1] = goatsoup_seed[1]
|
||||
goatsoup_rnd[2] = goatsoup_seed[2]
|
||||
goatsoup_rnd[3] = goatsoup_seed[3]
|
||||
}
|
||||
|
||||
sub random_name() -> str {
|
||||
ubyte ii
|
||||
str name = " " ; 8 chars max
|
||||
ubyte nx = 0
|
||||
for ii in 0 to goatsoup_rnd_number() & 3 {
|
||||
ubyte x = goatsoup_rnd_number() & $3e
|
||||
if pairs0[x] != '.' {
|
||||
name[nx] = pairs0[x]
|
||||
nx++
|
||||
}
|
||||
x++
|
||||
if pairs0[x] != '.' {
|
||||
name[nx] = pairs0[x]
|
||||
nx++
|
||||
}
|
||||
}
|
||||
name[nx] = 0
|
||||
name[0] |= 32 ; uppercase first letter
|
||||
return name
|
||||
}
|
||||
|
||||
sub goatsoup_rnd_number() -> ubyte {
|
||||
ubyte x = goatsoup_rnd[0] * 2
|
||||
uword a = x as uword + goatsoup_rnd[2]
|
||||
if goatsoup_rnd[0] > 127
|
||||
a ++
|
||||
goatsoup_rnd[0] = lsb(a)
|
||||
goatsoup_rnd[2] = x
|
||||
x = goatsoup_rnd[1]
|
||||
ubyte ac = x + goatsoup_rnd[3] + msb(a)
|
||||
goatsoup_rnd[1] = ac
|
||||
goatsoup_rnd[3] = x
|
||||
return ac
|
||||
}
|
||||
|
||||
sub distance(ubyte px, ubyte py) -> ubyte {
|
||||
uword ax
|
||||
uword ay
|
||||
if px>x
|
||||
ax=px-x
|
||||
else
|
||||
ax=x-px
|
||||
if py>y
|
||||
ay=py-y
|
||||
else
|
||||
ay=y-py
|
||||
ay /= 2
|
||||
ubyte d = sqrt16(ax*ax + ay*ay)
|
||||
if d>63
|
||||
return 255
|
||||
return d*4
|
||||
}
|
||||
|
||||
sub soup() -> str {
|
||||
str planet_result = " " * 160
|
||||
uword[6] source_stack
|
||||
ubyte stack_ptr = 0
|
||||
str start_source = "\x8F is \x97."
|
||||
uword source_ptr = &start_source
|
||||
uword result_ptr = &planet_result
|
||||
|
||||
reset_rnd()
|
||||
recursive_soup()
|
||||
return planet_result
|
||||
|
||||
sub recursive_soup() {
|
||||
repeat {
|
||||
ubyte c = @(source_ptr)
|
||||
source_ptr++
|
||||
if c == $00 {
|
||||
@(result_ptr) = 0
|
||||
return
|
||||
}
|
||||
else if c <= $80 {
|
||||
@(result_ptr) = c
|
||||
result_ptr++
|
||||
}
|
||||
else {
|
||||
if c <= $a4 {
|
||||
ubyte rnr = goatsoup_rnd_number()
|
||||
ubyte wordNr = (rnr >= $33) + (rnr >= $66) + (rnr >= $99) + (rnr >= $CC)
|
||||
source_stack[stack_ptr] = source_ptr
|
||||
stack_ptr++
|
||||
source_ptr = getword(c, wordNr)
|
||||
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
|
||||
stack_ptr--
|
||||
source_ptr = source_stack[stack_ptr]
|
||||
} else {
|
||||
if c == $b0 {
|
||||
@(result_ptr) = name[0] | 32
|
||||
result_ptr++
|
||||
concat_string(&name + 1)
|
||||
}
|
||||
else if c == $b1 {
|
||||
@(result_ptr) = name[0] | 32
|
||||
result_ptr++
|
||||
ubyte ni
|
||||
for ni in 1 to len(name) {
|
||||
ubyte cc = name[ni]
|
||||
if cc=='e' or cc=='o' or cc==0
|
||||
break
|
||||
else {
|
||||
@(result_ptr) = cc
|
||||
result_ptr++
|
||||
}
|
||||
}
|
||||
@(result_ptr) = 'i'
|
||||
result_ptr++
|
||||
@(result_ptr) = 'a'
|
||||
result_ptr++
|
||||
@(result_ptr) = 'n'
|
||||
result_ptr++
|
||||
}
|
||||
else if c == $b2 {
|
||||
concat_string(random_name())
|
||||
}
|
||||
else {
|
||||
@(result_ptr) = c
|
||||
result_ptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub concat_string(uword str_ptr) {
|
||||
repeat {
|
||||
ubyte c = @(str_ptr)
|
||||
if c==0
|
||||
break
|
||||
else {
|
||||
@(result_ptr) = c
|
||||
str_ptr++
|
||||
result_ptr++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub display(ubyte compressed) {
|
||||
if compressed {
|
||||
print_name_uppercase()
|
||||
txt.print(" TL:")
|
||||
txt.print_ub(techlevel+1)
|
||||
txt.chrout(' ')
|
||||
txt.print(econnames[economy])
|
||||
txt.chrout(' ')
|
||||
txt.print(govnames[govtype])
|
||||
} else {
|
||||
txt.print("\n\nSystem: ")
|
||||
print_name_uppercase()
|
||||
txt.print("\nPosition: ")
|
||||
txt.print_ub(x)
|
||||
txt.chrout('\'')
|
||||
txt.print_ub(y)
|
||||
txt.chrout(' ')
|
||||
txt.chrout('#')
|
||||
txt.print_ub(number)
|
||||
txt.print("\nEconomy: ")
|
||||
txt.print(econnames[economy])
|
||||
txt.print("\nGovernment: ")
|
||||
txt.print(govnames[govtype])
|
||||
txt.print("\nTech Level: ")
|
||||
txt.print_ub(techlevel+1)
|
||||
txt.print("\nTurnover: ")
|
||||
txt.print_uw(productivity)
|
||||
txt.print("\nRadius: ")
|
||||
txt.print_uw(radius)
|
||||
txt.print("\nPopulation: ")
|
||||
txt.print_ub(population >> 3)
|
||||
txt.print(" Billion\nSpecies: ")
|
||||
if species_is_alien {
|
||||
if species_size < len(species_sizes) {
|
||||
txt.print(species_sizes[species_size])
|
||||
txt.chrout(' ')
|
||||
}
|
||||
if species_color < len(species_colors) {
|
||||
txt.print(species_colors[species_color])
|
||||
txt.chrout(' ')
|
||||
}
|
||||
if species_look < len(species_looks) {
|
||||
txt.print(species_looks[species_look])
|
||||
txt.chrout(' ')
|
||||
}
|
||||
if species_kind < len(species_kinds) {
|
||||
txt.print(species_kinds[species_kind])
|
||||
}
|
||||
} else {
|
||||
txt.print("Human Colonials")
|
||||
}
|
||||
txt.chrout('\n')
|
||||
txt.print(soup())
|
||||
txt.chrout('\n')
|
||||
}
|
||||
}
|
||||
|
||||
sub print_name_uppercase() {
|
||||
ubyte c
|
||||
for c in name
|
||||
txt.chrout(c | 32)
|
||||
}
|
||||
|
||||
asmsub getword(ubyte list @A, ubyte wordidx @Y) -> uword @AY {
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_REG
|
||||
sec
|
||||
sbc #$81
|
||||
asl a
|
||||
tay
|
||||
lda wordlists,y
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda wordlists+1,y
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
tay
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
iny
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
tay
|
||||
pla
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
util {
|
||||
sub prefix_matches(uword prefixptr, uword stringptr) -> ubyte {
|
||||
ubyte ix=0
|
||||
repeat {
|
||||
ubyte pc = @(prefixptr)
|
||||
ubyte sc = @(stringptr)
|
||||
if pc == 0
|
||||
return true
|
||||
; to lowercase for case insensitive compare:
|
||||
pc &= 127
|
||||
sc &= 127
|
||||
if pc != sc
|
||||
return false
|
||||
prefixptr++
|
||||
stringptr++
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
sub print_right(ubyte width, uword string) {
|
||||
repeat width - strlen(string) {
|
||||
txt.chrout(' ')
|
||||
}
|
||||
txt.print(string)
|
||||
}
|
||||
|
||||
asmsub print_10s(uword value @AY) clobbers(A, X, Y) {
|
||||
%asm {{
|
||||
jsr conv.uword2decimal
|
||||
lda conv.uword2decimal.decTenThousands
|
||||
ldy #0 ; have we started printing?
|
||||
cmp #'0'
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
+ lda conv.uword2decimal.decThousands
|
||||
cmp #'0'
|
||||
bne +
|
||||
cpy #0
|
||||
beq ++
|
||||
+ jsr c64.CHROUT
|
||||
iny
|
||||
+ lda conv.uword2decimal.decHundreds
|
||||
cmp #'0'
|
||||
bne +
|
||||
cpy #0
|
||||
beq ++
|
||||
+ jsr c64.CHROUT
|
||||
iny
|
||||
+ lda conv.uword2decimal.decTens
|
||||
jsr c64.CHROUT
|
||||
lda #'.'
|
||||
jsr c64.CHROUT
|
||||
lda conv.uword2decimal.decOnes
|
||||
jsr c64.CHROUT
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub testX() {
|
||||
%asm {{
|
||||
stx _saveX
|
||||
lda #13
|
||||
jsr txt.chrout
|
||||
lda _saveX
|
||||
jsr txt.print_ub
|
||||
lda #13
|
||||
jsr txt.chrout
|
||||
ldx _saveX
|
||||
rts
|
||||
_saveX .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
%import graphics
|
||||
%zeropage floatsafe
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
@ -172,10 +172,10 @@ expression :
|
||||
| left = expression EOL? bop = ('+' | '-' ) EOL? right = expression
|
||||
| left = expression EOL? bop = ('<<' | '>>' ) EOL? right = expression
|
||||
| left = expression EOL? bop = ('<' | '>' | '<=' | '>=') EOL? right = expression
|
||||
| left = expression EOL? bop = ('==' | '!=') EOL? right = expression
|
||||
| left = expression EOL? bop = '&' EOL? right = expression
|
||||
| left = expression EOL? bop = '^' EOL? right = expression
|
||||
| left = expression EOL? bop = '|' EOL? right = expression
|
||||
| left = expression EOL? bop = ('==' | '!=') EOL? right = expression
|
||||
| rangefrom = expression rto = ('to'|'downto') rangeto = expression ('step' rangestep = expression)? // can't create separate rule due to mutual left-recursion
|
||||
| left = expression EOL? bop = 'and' EOL? right = expression
|
||||
| left = expression EOL? bop = 'or' EOL? right = expression
|
||||
|
Reference in New Issue
Block a user