Compare commits

...

57 Commits
v4.4 ... v4.5

Author SHA1 Message Date
07cce3b3fc version 4.5 2020-10-11 21:59:38 +02:00
f2c19afd95 version 4.5 2020-10-11 21:47:41 +02:00
d159e70e1c textelite travel commands 2020-10-11 21:38:25 +02:00
ac693a2541 textelite buy and sell commands 2020-10-11 19:29:18 +02:00
1e988116ce fixed precedence of comparison and bitwise operators 2020-10-11 19:02:53 +02:00
ec9e722927 added conv.str2byte and conv.str2ubyte 2020-10-11 18:36:20 +02:00
4cd5e8c378 textelite 2020-10-11 18:19:09 +02:00
b759d5e06a fixed X register corruption on Cx16 verions of float.GIVUAYFAY and GIVAYFAY 2020-10-11 17:46:19 +02:00
1469033c1e todo 2020-10-11 16:53:00 +02:00
c15fd75df7 asmassignment can now use arbitrary source symbols; optimized byte-word sign extesion with this to not use stack anymore 2020-10-11 15:44:08 +02:00
73524e01a6 really fix byte-word sign extension for function args as expression 2020-10-11 03:07:45 +02:00
9e54e11113 fixed string + string/ string * number 2020-10-11 02:34:04 +02:00
01ac5f29db fix byte-word sign extension for function args as expression 2020-10-11 01:38:34 +02:00
67a2241e32 textelite market start 2020-10-11 00:38:38 +02:00
72b6dc3de7 avoid crash when optimizer has multiple replacements of the same node 2020-10-11 00:37:35 +02:00
6f5b645995 textelite market start 2020-10-10 23:24:15 +02:00
458ad1de57 fix strlen on uword (pointer) instead of str 2020-10-10 23:24:05 +02:00
216f48b7c1 txtelite 2020-10-10 22:45:03 +02:00
b2d1757e5a asmgen: byte to word sign extensions 2020-10-10 15:39:48 +02:00
6e53eb9d5c asmgen: only generate storage byte for register saves in subroutine when it's actually needed 2020-10-10 15:02:56 +02:00
e5ee5be9c5 textelite 2020-10-10 04:42:17 +02:00
bd237b2b95 it's now possible in more places to assign arrays and put array literals without the need to define explicit variable. 2020-10-10 04:30:28 +02:00
d31cf766eb added missing doc picture 2020-10-10 02:51:02 +02:00
56d530ff04 txtelite with input loop 2020-10-10 01:46:19 +02:00
0bbb2240f2 txtelite with input loop 2020-10-10 01:35:46 +02:00
1c8e4dba73 added \' escape character 2020-10-10 01:28:57 +02:00
4a9956c4a4 txtelite species and planet naming fix 2020-10-10 01:15:26 +02:00
59c0e6ae32 added some more missing assignment codegens (word * byte etc) 2020-10-09 23:48:33 +02:00
94c30fc21e textelite 2020-10-09 22:47:42 +02:00
8bb3b3be20 fix repeat loop for variables when var == 0 2020-10-09 22:30:21 +02:00
85e3c2c5a2 textelite 2020-10-09 22:25:12 +02:00
4be381c597 fixed compiler optimizer crash because of conflicting expression replacements 2020-10-09 21:51:54 +02:00
6ff5470cf1 txtelite 2020-10-09 21:01:06 +02:00
151dcfdef9 code style 2020-10-08 21:47:07 +02:00
c282b4cb9f code style 2020-10-07 23:24:30 +02:00
c426f4626c added some more missing aug assign operator code 2020-10-07 22:53:18 +02:00
0e3c92626e fixed handling of main module when importing another. fixed diskdir closedown. 2020-10-07 21:55:00 +02:00
5099525e24 added missing register pair assignments. fixed compiler crashes 2020-10-07 03:43:02 +02:00
e22b4cbb67 fixed invalid errormessage about memory mapped strings 2020-10-07 01:35:39 +02:00
2b48828179 examples issues 2020-10-07 01:21:41 +02:00
3e181362dd optimized code for processing return values from asmsubs without intermediate estack. 2020-10-07 00:51:57 +02:00
71fd98e39e allow asmsub routines with multiple return values to be called (special case for return values in status register) 2020-10-07 00:33:42 +02:00
71cd8b6d51 cx16 cross-compile teaser screenshot 2020-10-05 19:59:51 +02:00
ad75fcbf7e txtelite 2020-10-05 19:49:13 +02:00
f8b04a6357 added status return flags to some kernel i/o operations 2020-10-05 19:48:21 +02:00
d8fcbb78d3 txtelite goatsoup 2020-10-04 21:53:16 +02:00
8408bf3789 another compiler crash fixed when dealing with functioncall returning a str 2020-10-04 21:53:08 +02:00
3e1185658e txtelite goatsoup 2020-10-04 21:35:37 +02:00
d778cdcd61 another compiler crash fixed when dealing with functioncall returning a str 2020-10-04 21:11:42 +02:00
90b303fc03 fix error message for invalid number of arguments 2020-10-04 19:28:22 +02:00
eb86b1270d txtelite 2020-10-04 19:23:36 +02:00
a1f0cc878b correct error message for faulty string variable declarations 2020-10-04 19:13:19 +02:00
f2e2720b15 compiler crash fixed when dealing with functioncall returning a str 2020-10-04 18:47:47 +02:00
ec8cfe1591 make string-assignment actually work (using strcpy) 2020-10-04 18:18:58 +02:00
22eac159e5 txtelite 2020-10-04 17:47:57 +02:00
956b0c3fa7 added \xHH escape character to strings, allow strings of length zero. 2020-10-04 13:05:43 +02:00
a6427e0949 added \$HH escape character to strings 2020-10-03 15:11:09 +02:00
55 changed files with 2270 additions and 743 deletions

View File

@ -204,19 +204,19 @@ romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word 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 $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 $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 $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 $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 $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 $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 $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 $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 $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 $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 $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 @ A ; (via 810 ($32A)) get a character 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 $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock 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 $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns

View File

@ -249,6 +249,28 @@ output .text "0000", $00 ; 0-terminated output buffer (to make printing ea
}} }}
} }
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 { asmsub str2uword(str string @ AY) -> uword @ AY {
; -- returns the unsigned word value of the string number argument in 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 ; the number may NOT be preceded by a + sign and may NOT contain spaces

View File

@ -78,10 +78,10 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1 ; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{ %asm {{
phx phx
sta P8ZP_SCRATCH_REG sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_B1 sty P8ZP_SCRATCH_B1
tya tya
ldy P8ZP_SCRATCH_REG ldy P8ZP_SCRATCH_W2
jsr GIVAYF ; load it as signed... correct afterwards jsr GIVAYF ; load it as signed... correct afterwards
lda P8ZP_SCRATCH_B1 lda P8ZP_SCRATCH_B1
bpl + bpl +
@ -98,9 +98,9 @@ _flt65536 .byte 145,0,0,0,0 ; 65536.0
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) { asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1 ; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{ %asm {{
sta P8ZP_SCRATCH_REG sta P8ZP_SCRATCH_W2
tya tya
ldy P8ZP_SCRATCH_REG ldy P8ZP_SCRATCH_W2
jmp GIVAYF ; this uses the inverse order, Y/A jmp GIVAYF ; this uses the inverse order, Y/A
}} }}
} }

View File

@ -16,44 +16,44 @@ c64 {
; CLEARSCR -> use screen.clear_screen ; CLEARSCR -> use screen.clear_screen
; HOMECRSR -> use screen.plot ; HOMECRSR -> use screen.plot
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip 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 $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 $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors 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 $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 $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 $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 $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 $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 $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 $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 $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 $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN 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 $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 $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word 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 $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 $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 $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 $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 $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 $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 $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 $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 $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 $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 $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 @ A ; (via 810 ($32A)) get a character 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 $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock 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 $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 c64scr.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
} }

View File

@ -781,7 +781,7 @@ mul_byte_3 .proc
sta P8ZP_SCRATCH_REG sta P8ZP_SCRATCH_REG
asl a asl a
clc clc
adc P8P_P8ZP_SCRATCH_REG adc P8ZP_SCRATCH_REG
rts rts
.pend .pend

View File

@ -682,6 +682,7 @@ func_read_flags .proc
func_sqrt16 .proc func_sqrt16 .proc
; TODO is this one faster? http://6502org.wikidot.com/software-math-sqrt
lda P8ESTACK_LO+1,x lda P8ESTACK_LO+1,x
sta P8ZP_SCRATCH_W2 sta P8ZP_SCRATCH_W2
lda P8ESTACK_HI+1,x lda P8ESTACK_HI+1,x
@ -1976,7 +1977,7 @@ ror2_array_uw .proc
strcpy .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. ; it is assumed the target string is large enough.
sta P8ZP_SCRATCH_W2 sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1 sty P8ZP_SCRATCH_W2+1

View File

@ -5,5 +5,5 @@
; indent format: TABS, size=8 ; indent format: TABS, size=8
prog8_lib { prog8_lib {
%asminclude "library:prog8lib.asm", "" %asminclude "library:prog8_lib.asm", ""
} }

View File

@ -1 +1 @@
4.4 4.5

View File

@ -6,6 +6,7 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import java.nio.file.Path import java.nio.file.Path
@ -44,11 +45,19 @@ interface IFunctionCall {
var args: MutableList<Expression> var args: MutableList<Expression>
} }
class AsmGenInfo {
var usedRegsaveA = false
var usedRegsaveX = false
var usedRegsaveY = false
}
interface INameScope { interface INameScope {
val name: String val name: String
val position: Position val position: Position
val statements: MutableList<Statement> val statements: MutableList<Statement>
val parent: Node val parent: Node
val asmGenInfo: AsmGenInfo
fun linkParents(parent: Node) fun linkParents(parent: Node)
@ -260,10 +269,14 @@ class Module(override val name: String,
override lateinit var parent: Node override lateinit var parent: Node
lateinit var program: Program lateinit var program: Program
override val asmGenInfo = AsmGenInfo()
val importedBy = mutableListOf<Module>() val importedBy = mutableListOf<Module>()
val imports = mutableSetOf<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) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -290,6 +303,7 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val position = Position("<<<global>>>", 0, 0, 0) override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>() override val statements = mutableListOf<Statement>()
override var parent: Node = ParentSentinel override var parent: Node = ParentSentinel
override val asmGenInfo = AsmGenInfo()
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
modules.forEach { it.linkParents(this) } modules.forEach { it.linkParents(this) }
@ -342,6 +356,7 @@ object BuiltinFunctionScopePlaceholder : INameScope {
override val position = Position("<<placeholder>>", 0, 0, 0) override val position = Position("<<placeholder>>", 0, 0, 0)
override var statements = mutableListOf<Statement>() override var statements = mutableListOf<Statement>()
override var parent: Node = ParentSentinel override var parent: Node = ParentSentinel
override val asmGenInfo = AsmGenInfo()
override fun linkParents(parent: Node) {} override fun linkParents(parent: Node) {}
} }

View File

@ -647,7 +647,21 @@ 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 es2 = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
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 { internal fun unescape(str: String, position: Position): String {
val result = mutableListOf<Char>() val result = mutableListOf<Char>()
@ -661,9 +675,15 @@ internal fun unescape(str: String, position: Position): String {
'n' -> '\n' 'n' -> '\n'
'r' -> '\r' 'r' -> '\r'
'"' -> '"' '"' -> '"'
'\'' -> '\''
'u' -> { 'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar() "${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 -> throw SyntaxError("invalid escape char in string: \\$ec", position)
}) })
} else { } else {

View File

@ -39,16 +39,20 @@ enum class DataType {
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it } infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
infix fun largerThan(other: DataType) = infix fun largerThan(other: DataType) =
when(this) { when {
in ByteDatatypes -> false this == other -> false
in WordDatatypes -> other in ByteDatatypes this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> false
else -> true else -> true
} }
infix fun equalsSize(other: DataType) = infix fun equalsSize(other: DataType) =
when(this) { when {
in ByteDatatypes -> other in ByteDatatypes this == other -> true
in WordDatatypes -> other in WordDatatypes this in ByteDatatypes -> other in ByteDatatypes
this in WordDatatypes -> other in WordDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> true
else -> false else -> false
} }

View File

@ -3,6 +3,7 @@ package prog8.ast.base
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.processing.* import prog8.ast.processing.*
import prog8.ast.statements.Directive
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.BeforeAsmGenerationAstChanger import prog8.compiler.BeforeAsmGenerationAstChanger
@ -18,8 +19,8 @@ internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
fixer.applyModifications() fixer.applyModifications()
} }
internal fun Program.reorderStatements() { internal fun Program.reorderStatements(errors: ErrorReporter) {
val reorder = StatementReorderer(this) val reorder = StatementReorderer(this, errors)
reorder.visit(this) reorder.visit(this)
reorder.applyModifications() reorder.applyModifications()
} }
@ -56,6 +57,9 @@ internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val transforms = AstVariousTransforms(this) val transforms = AstVariousTransforms(this)
transforms.visit(this) transforms.visit(this)
transforms.applyModifications() transforms.applyModifications()
val lit2decl = LiteralsToAutoVars(this)
lit2decl.visit(this)
lit2decl.applyModifications()
} }
if (modules.map { it.name }.toSet().size != modules.size) { if (modules.map { it.name }.toSet().size != modules.size) {
@ -68,3 +72,37 @@ internal fun Program.variousCleanups() {
process.visit(this) process.visit(this)
process.applyModifications() 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)
}
}
}

View File

@ -872,6 +872,12 @@ class FunctionCall(override var target: IdentifierReference,
return InferredTypes.void() // no return value return InferredTypes.void() // no return value
if(stmt.returntypes.size==1) if(stmt.returntypes.size==1)
return InferredTypes.knownFor(stmt.returntypes[0]) 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 return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
} }
else -> return InferredTypes.unknown() else -> return InferredTypes.unknown()

View File

@ -346,17 +346,15 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(assignment: Assignment) { override fun visit(assignment: Assignment) {
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
if(assignment.value is FunctionCall) { if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace) val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if (stmt is Subroutine && stmt.isAsmSubroutine) { if (stmt is Subroutine) {
if(stmt.returntypes.size>1) val idt = assignment.target.inferType(program, assignment)
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) if(!idt.isKnown) {
else { errors.err("return type mismatch", assignment.value.position)
val idt = assignment.target.inferType(program, assignment) }
if(!idt.isKnown || stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) { if(stmt.returntypes.size <= 1 && stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
errors.err("return type mismatch", assignment.value.position) errors.err("return type mismatch", assignment.value.position)
}
} }
} }
} }
@ -386,7 +384,8 @@ internal class AstChecker(private val program: Program,
} }
val targetDt = assignment.target.inferType(program, assignment) 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) if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
errors.err("cannot assign value to string or array", assignment.value.position) errors.err("cannot assign value to string or array", assignment.value.position)
else else
@ -446,12 +445,8 @@ internal class AstChecker(private val program: Program,
checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal) checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal)
} else { } else {
val sourceDatatype = assignment.value.inferType(program) val sourceDatatype = assignment.value.inferType(program)
if (!sourceDatatype.isKnown) { if (sourceDatatype.isUnknown) {
if (assignment.value is FunctionCall) { 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
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position) errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
} else { } else {
checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget, checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
@ -632,6 +627,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) super.visit(decl)
} }
@ -753,8 +756,13 @@ internal class AstChecker(private val program: Program,
return e is StringLiteralValue return e is StringLiteralValue
} }
if(!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) }) if(array.parent is VarDecl) {
errors.err("array literal contains invalid types", array.position) 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) super.visit(array)
} }
@ -783,6 +791,8 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(expr: BinaryExpression) { override fun visit(expr: BinaryExpression) {
super.visit(expr)
val leftIDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program) val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown) if(!leftIDt.isKnown || !rightIDt.isKnown)
@ -822,13 +832,12 @@ internal class AstChecker(private val program: Program,
} }
} }
if(leftDt !in NumericDatatypes) if(leftDt !in NumericDatatypes && leftDt != DataType.STR)
errors.err("left operand is not numeric", expr.left.position) errors.err("left operand is not numeric or str", expr.left.position)
if(rightDt!in NumericDatatypes) if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
errors.err("right operand is not numeric", expr.right.position) errors.err("right operand is not numeric or str", expr.right.position)
if(leftDt!=rightDt) if(leftDt!=rightDt)
errors.err("left and right operands aren't the same type", expr.left.position) errors.err("left and right operands aren't the same type", expr.left.position)
super.visit(expr)
} }
override fun visit(typecast: TypecastExpression) { override fun visit(typecast: TypecastExpression) {
@ -888,7 +897,29 @@ internal class AstChecker(private val program: Program,
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program) val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program)
if(error!=null) 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) super.visit(functionCall)
} }

View File

@ -143,8 +143,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
} }
override fun visit(string: StringLiteralValue) { override fun visit(string: StringLiteralValue) {
if (string.value.length !in 1..255) if (string.value.length > 255)
errors.err("string literal length must be between 1 and 255", string.position) errors.err("string literal length max is 255", string.position)
super.visit(string) super.visit(string)
} }

View File

@ -47,78 +47,59 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
return noModifications return noModifications
} }
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
when { val leftStr = expr.left as? StringLiteralValue
expr.left is StringLiteralValue -> val rightStr = expr.right as? StringLiteralValue
return listOf(IAstModification.ReplaceNode( if(expr.operator == "+") {
expr, val concatenatedString = concatString(expr)
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr), if(concatenatedString!=null)
parent return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent))
))
expr.right is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
parent
))
} }
else if(expr.operator == "*") {
return noModifications if (leftStr!=null) {
} val amount = expr.right.constValue(program)
if(amount!=null) {
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> { val string = leftStr.value.repeat(amount.number.toInt())
if(string.parent !is VarDecl) { val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position)
// replace the literal string by a identifier reference to a new local vardecl return listOf(IAstModification.ReplaceNode(expr, strval, parent))
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 { else if (rightStr!=null) {
val arrayDt = array.guessDatatype(program) val amount = expr.right.constValue(program)
if(arrayDt.isKnown) { if(amount!=null) {
// this array literal is part of an expression, turn it into an identifier reference val string = rightStr.value.repeat(amount.number.toInt())
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT)) val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position)
if(litval2!=null && litval2!=array) { return listOf(IAstModification.ReplaceNode(expr, strval, parent))
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)
)
} }
} }
} }
return noModifications return noModifications
} }
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression { private fun concatString(expr: BinaryExpression): StringLiteralValue? {
val constvalue = operand.constValue(program) val rightStrval = expr.right as? StringLiteralValue
if(constvalue!=null) { val leftStrval = expr.left as? StringLiteralValue
if (expr.operator == "*") { return when {
// repeat a string a number of times expr.operator!="+" -> null
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position) 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
} }
} }

View 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) {
// 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 {
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() as Node)
)
}
}
}
return noModifications
}
}

View File

@ -6,7 +6,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.* 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. // Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set. // - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last. // - library blocks are put last.
@ -84,20 +84,31 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program) val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment) 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 )) { if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
val assignments = if (assignment.value is ArrayLiteralValue) { assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] ' flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
} else { } 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) } if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
modifications.add(IAstModification.Remove(assignment, parent)) assignments = if (assignment.value is ArrayLiteralValue) {
return modifications flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
} else {
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
} }
} }
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
modifications.add(IAstModification.Remove(assignment, parent))
return modifications
}
return noModifications return noModifications
} }
@ -150,15 +161,69 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return noModifications return noModifications
} }
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> { private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
// TODO use a pointer loop instead of individual assignments
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val alv = assign.value as? ArrayLiteralValue
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", assign.position)
return emptyList()
}
return alv.value.withIndex().map { (index, value)->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, assign.position), assign.position), assign.position)
Assignment(AssignTarget(null, idx, null, assign.position), value, value.position)
}
}
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
// TODO use a pointer loop instead of individual assignments
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()
}
if(targetVar.arraysize==null) {
errors.err("array has no defined size", identifier.position)
return emptyList()
}
val alv = sourceVar.value as? ArrayLiteralValue
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
errors.err("element count mismatch", assign.position)
return emptyList()
}
return alv.value.withIndex().map { (index, value)->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, assign.position), assign.position), assign.position)
Assignment(AssignTarget(null, idx, null, assign.position), value, value.position)
}
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
val identifier = structAssignment.target.identifier!! val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single() val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!! val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!! val struct = targetVar.struct!!
val slv = structAssignment.value as? ArrayLiteralValue val slv = structAssignment.value as? ArrayLiteralValue
if(slv==null || slv.value.size != struct.numberOfElements) if(slv==null || slv.value.size != struct.numberOfElements) {
throw FatalAstException("element count mismatch") errors.err("element count mismatch", structAssignment.position)
return emptyList()
}
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) -> return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
targetDecl as VarDecl targetDecl as VarDecl
@ -171,7 +236,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 identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single() val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!! val targetVar = identifier.targetVarDecl(program.namespace)!!

View File

@ -124,10 +124,12 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
call as Node) call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) { } else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead. // we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
modifications += IAstModification.ReplaceNode( if(arg.second.value is IdentifierReference) {
call.args[arg.second.index], modifications += IAstModification.ReplaceNode(
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position), call.args[arg.second.index],
call as Node) AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node)
}
} else if(arg.second.value is NumericLiteralValue) { } else if(arg.second.value is NumericLiteralValue) {
val cast = (arg.second.value as NumericLiteralValue).cast(requiredType) val cast = (arg.second.value as NumericLiteralValue).cast(requiredType)
if(cast.isValid) if(cast.isValid)

View File

@ -4,10 +4,9 @@ import prog8.ast.IFunctionCall
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder import prog8.ast.statements.*
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.compiler.CompilerException import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions 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 argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope) val target = call.target.targetStatement(scope)
if (target is Subroutine) { if (target is Subroutine) {
// asmsub types are not checked specifically at this time
if(call.args.size != target.parameters.size) if(call.args.size != target.parameters.size)
return "invalid number of arguments" return "invalid number of arguments"
val paramtypes = target.parameters.map { it.type } val paramtypes = target.parameters.map { it.type }
@ -53,6 +51,16 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
val expected = paramtypes[mismatch].toString() val expected = paramtypes[mismatch].toString()
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected" 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) { else if (target is BuiltinFunctionStatementPlaceholder) {
val func = BuiltinFunctions.getValue(target.name) val func = BuiltinFunctions.getValue(target.name)

View File

@ -57,6 +57,7 @@ class Block(override val name: String,
val isInLibrary: Boolean, val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope { override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node override lateinit var parent: Node
override val asmGenInfo = AsmGenInfo()
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -234,7 +235,8 @@ open class VarDecl(val type: VarDeclType,
} }
override fun replaceChildNode(node: Node, replacement: Node) { 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 value = replacement
replacement.parent = this replacement.parent = this
} }
@ -416,7 +418,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
} }
} }
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) { if (identifier != null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown() val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype) if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
@ -609,6 +611,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() { override val position: Position) : INameScope, Statement() {
override val name: String override val name: String
override lateinit var parent: Node override lateinit var parent: Node
override val asmGenInfo = AsmGenInfo()
companion object { companion object {
private var sequenceNumber = 1 private var sequenceNumber = 1
@ -662,6 +665,7 @@ class Subroutine(override val name: String,
override val position: Position) : Statement(), INameScope { override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node override lateinit var parent: Node
override val asmGenInfo = AsmGenInfo()
val scopedname: String by lazy { makeScopedName(name) } val scopedname: String by lazy { makeScopedName(name) }
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -939,6 +943,7 @@ class StructDecl(override val name: String,
override val position: Position): Statement(), INameScope { override val position: Position): Statement(), INameScope {
override lateinit var parent: Node override lateinit var parent: Node
override val asmGenInfo = AsmGenInfo()
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent

View File

@ -161,11 +161,13 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
if(sourceDt in PassByReferenceDatatypes) { if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) { if(typecast.type==DataType.UWORD) {
return listOf(IAstModification.ReplaceNode( if(typecast.expression is IdentifierReference) {
typecast, return listOf(IAstModification.ReplaceNode(
AddressOf(typecast.expression as IdentifierReference, typecast.position), typecast,
parent AddressOf(typecast.expression as IdentifierReference, typecast.position),
)) parent
))
}
} else { } else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position) errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
} }

View File

@ -107,9 +107,9 @@ private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program,
// depending on the machine and compiler options we may have to include some libraries // depending on the machine and compiler options we may have to include some libraries
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst) 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, "math")
importer.importLibraryModule(programAst, "prog8lib") importer.importLibraryModule(programAst, "prog8_lib")
errors.handle() errors.handle()
return Triple(programAst, compilerOptions, importedFiles) return Triple(programAst, compilerOptions, importedFiles)
} }
@ -120,8 +120,6 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
as? Directive)?.args?.single()?.name?.toUpperCase() as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" } val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase() 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" } val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase() 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() val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
@ -173,7 +171,8 @@ private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptio
errors.handle() errors.handle()
programAst.constantFold(errors) programAst.constantFold(errors)
errors.handle() errors.handle()
programAst.reorderStatements() programAst.reorderStatements(errors)
errors.handle()
programAst.addTypecasts(errors) programAst.addTypecasts(errors)
errors.handle() errors.handle()
programAst.variousCleanups() programAst.variousCleanups()
@ -189,10 +188,11 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
while (true) { while (true) {
// keep optimizing expressions and statements until no more steps remain // keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions() 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 programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
errors.handle() errors.handle()
if (optsDone1 + optsDone2 == 0) if (optsDone1 + optsDone2 + optsDone3 == 0)
break break
} }
@ -211,6 +211,7 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
programAst.checkRecursion(errors) // check if there are recursive subroutine calls programAst.checkRecursion(errors) // check if there are recursive subroutine calls
errors.handle() errors.handle()
programAst.verifyFunctionArgTypes() programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
} }
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path, private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,

View File

@ -1054,11 +1054,16 @@ object Petscii {
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
return text.map { return text.map {
val petscii = lookup[it] val petscii = lookup[it]
petscii?.toShort() ?: if(it=='\u0000') petscii?.toShort() ?: when (it) {
0.toShort() '\u0000' -> 0.toShort()
else { in '\u8000'..'\u80ff' -> {
val case = if (lowercase) "lower" else "upper" // special case: take the lower 8 bit hex value directly
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})") (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 val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
return text.map{ return text.map{
val screencode = lookup[it] val screencode = lookup[it]
screencode?.toShort() ?: if(it=='\u0000') screencode?.toShort() ?: when (it) {
0.toShort() '\u0000' -> 0.toShort()
else { in '\u8000'..'\u80ff' -> {
val case = if (lowercase) "lower" else "upper" // special case: take the lower 8 bit hex value directly
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})") (it.toInt() - 0x8000).toShort()
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
}
} }
} }
} }

View File

@ -194,11 +194,10 @@ internal class AsmGen(private val program: Program,
} }
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) { private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
val variable = IdentifierReference(variableName, decl.position) val asmName = asmVariableName(variableName)
variable.linkParents(decl.parent)
val asgn = AsmAssignment( val asgn = AsmAssignment(
AsmAssignSource.fromAstSource(decl.value!!, program), AsmAssignSource.fromAstSource(decl.value!!, program, this),
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, variable = variable), AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, decl.definingSubroutine(), variableAsmName = asmName),
false, decl.position) false, decl.position)
assignmentAsmGen.translateNormalAssignment(asgn) assignmentAsmGen.translateNormalAssignment(asgn)
} }
@ -496,8 +495,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> { 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 sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program.namespace)!! val vardecl = pointervar.targetVarDecl(program.namespace)!!
val scopedName = vardecl.makeScopedName(vardecl.name) val scopedName = vardecl.makeScopedName(vardecl.name)
@ -538,35 +543,72 @@ 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>()
private val saveRegisterLabels = Stack<String>(); internal fun saveRegister(register: CpuRegister, dontUseStack: Boolean, scope: Subroutine?) {
if(dontUseStack) {
internal fun saveRegister(register: CpuRegister) { when (register) {
when(register) { CpuRegister.A -> {
CpuRegister.A -> out(" pha") out(" sta _prog8_regsaveA")
CpuRegister.X -> { if (scope != null)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx") scope.asmGenInfo.usedRegsaveA = true
else out(" stx _prog8_regsave${register.name}") }
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 {
else out(" sty _prog8_regsave${register.name}") 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) { internal fun restoreRegister(register: CpuRegister, dontUseStack: Boolean) {
when(register) { if(dontUseStack) {
CpuRegister.A -> out(" pla") when (register) {
CpuRegister.X -> { CpuRegister.A -> out(" sta _prog8_regsaveA")
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx") CpuRegister.X -> out(" ldx _prog8_regsaveX")
else out(" ldx _prog8_regsave${register.name}") CpuRegister.Y -> out(" ldy _prog8_regsaveY")
} }
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply") } else {
else out(" ldy _prog8_regsave${register.name}") 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 +630,10 @@ internal class AsmGen(private val program: Program,
if (builtinFunc != null) { if (builtinFunc != null) {
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc) builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
} else { } else {
functioncallAsmGen.translateFunctionCall(stmt)
// discard any results from the stack:
val sub = stmt.target.targetSubroutine(program.namespace)!! 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) val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((t, reg) in returns) { for ((t, reg) in returns) {
if (reg.stack) { if (reg.stack) {
@ -598,6 +641,8 @@ internal class AsmGen(private val program: Program,
else if (t == DataType.FLOAT) out(" inx | inx | inx") else if (t == DataType.FLOAT) out(" inx | inx | inx")
} }
} }
if(preserveStatusRegisterAfterCall)
out(" plp\t; restore status flags from call")
} }
} }
is Assignment -> assignmentAsmGen.translate(stmt) is Assignment -> assignmentAsmGen.translate(stmt)
@ -752,6 +797,7 @@ internal class AsmGen(private val program: Program,
CpuRegister.Y -> out(" tay") CpuRegister.Y -> out(" tay")
} }
} }
else -> throw AssemblyError("weird dt")
} }
} }
} }
@ -763,8 +809,8 @@ internal class AsmGen(private val program: Program,
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) = internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature) builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
internal fun translateFunctionCall(functionCall: FunctionCall) = internal fun translateFunctionCall(functionCall: FunctionCall, preserveStatusRegisterAfterCall: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCall) functioncallAsmGen.translateFunctionCall(functionCall, preserveStatusRegisterAfterCall)
internal fun translateNormalAssignment(assign: AsmAssignment) = internal fun translateNormalAssignment(assign: AsmAssignment) =
assignmentAsmGen.translateNormalAssignment(assign) assignmentAsmGen.translateNormalAssignment(assign)
@ -806,10 +852,13 @@ internal class AsmGen(private val program: Program,
out("; statements") out("; statements")
sub.statements.forEach{ translate(it) } sub.statements.forEach{ translate(it) }
out("; variables") out("; variables")
out(""" out("; register saves")
; register saves if(sub.asmGenInfo.usedRegsaveA)
_prog8_regsaveX .byte 0 out("_prog8_regsaveA .byte 0")
_prog8_regsaveY .byte 0""") // TODO only generate these bytes if they're actually used by saveRegister() if(sub.asmGenInfo.usedRegsaveX)
out("_prog8_regsaveX .byte 0")
if(sub.asmGenInfo.usedRegsaveY)
out("_prog8_regsaveY .byte 0")
vardecls2asm(sub.statements) vardecls2asm(sub.statements)
out(" .pend\n") out(" .pend\n")
} }
@ -937,7 +986,11 @@ _prog8_regsaveY .byte 0""") // TODO only generate these bytes if the
// note: A/Y must have been loaded with the number of iterations already! // note: A/Y must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter") val counterVar = makeLabel("repeatcounter")
out(""" out("""
sta $counterVar bne +
cpy #0
bne +
beq $endLabel
+ sta $counterVar
sty $counterVar+1 sty $counterVar+1
$repeatLabel lda $counterVar $repeatLabel lda $counterVar
bne + bne +
@ -966,6 +1019,7 @@ $counterVar .word 0""")
// note: A must have been loaded with the number of iterations already! // note: A must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter") val counterVar = makeLabel("repeatcounter")
out(""" out("""
beq $endLabel
sta $counterVar sta $counterVar
$repeatLabel""") $repeatLabel""")
translate(body) translate(body)
@ -1169,4 +1223,40 @@ $counterVar .byte 0""")
val assembly = asm.assembly.trimEnd().trimStart('\n') val assembly = asm.assembly.trimEnd().trimStart('\n')
assemblyLines.add(assembly) 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")
}
}
} }

View File

@ -390,12 +390,22 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcStrlen(fcall: IFunctionCall) { private fun funcStrlen(fcall: IFunctionCall) {
val name = asmgen.asmVariableName(fcall.args[0] as IdentifierReference) val name = asmgen.asmVariableName(fcall.args[0] as IdentifierReference)
asmgen.out(""" val type = fcall.args[0].inferType(program)
lda #<$name when {
ldy #>$name type.istype(DataType.STR) -> asmgen.out("""
jsr prog8_lib.strlen lda #<$name
sta P8ESTACK_LO,x ldy #>$name
dex""") 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) { private fun funcSwap(fcall: IFunctionCall) {
@ -488,9 +498,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// all other types of swap() calls are done via the evaluation stack // all other types of swap() calls are done via the evaluation stack
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget { fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
return when (expr) { return when (expr) {
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, variable=expr) is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, expr.definingSubroutine(), variableAsmName = asmgen.asmVariableName(expr))
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, array = expr) is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, expr.definingSubroutine(), array = expr)
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, memory = DirectMemoryWrite(expr.addressExpression, expr.position)) is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, expr.definingSubroutine(), memory = DirectMemoryWrite(expr.addressExpression, expr.position))
else -> throw AssemblyError("invalid expression object $expr") else -> throw AssemblyError("invalid expression object $expr")
} }
} }
@ -499,12 +509,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.translateExpression(second) asmgen.translateExpression(second)
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT) val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
val assignFirst = AsmAssignment( val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, datatype), AsmAssignSource(SourceStorageKind.STACK, program, asmgen, datatype),
targetFromExpr(first, datatype), targetFromExpr(first, datatype),
false, first.position false, first.position
) )
val assignSecond = AsmAssignment( val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, datatype), AsmAssignSource(SourceStorageKind.STACK, program, asmgen, datatype),
targetFromExpr(second, datatype), targetFromExpr(second, datatype),
false, second.position false, second.position
) )

View File

@ -22,7 +22,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is DirectMemoryRead -> translateDirectMemReadExpression(expression, true) is DirectMemoryRead -> translateDirectMemReadExpression(expression, true)
is NumericLiteralValue -> translateExpression(expression) is NumericLiteralValue -> translateExpression(expression)
is IdentifierReference -> 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 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") is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
} }
@ -942,14 +942,15 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
asmgen.out(" jsr floats.notequal_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel") asmgen.out(" jsr floats.notequal_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
} }
private fun translateExpression(expression: FunctionCall) { private fun translateFunctionCallResultOntoStack(expression: FunctionCall) {
val functionName = expression.target.nameInSource.last() val functionName = expression.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName] val builtinFunc = BuiltinFunctions[functionName]
if (builtinFunc != null) { if (builtinFunc != null) {
asmgen.translateFunctioncallExpression(expression, builtinFunc) asmgen.translateFunctioncallExpression(expression, builtinFunc)
} else { } else {
val sub = expression.target.targetSubroutine(program.namespace)!! 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) val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) { for ((_, reg) in returns) {
if (!reg.stack) { if (!reg.stack) {
@ -992,11 +993,11 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
} }
private fun translateExpression(expr: TypecastExpression) { private fun translateExpression(typecast: TypecastExpression) {
translateExpression(expr.expression) translateExpression(typecast.expression)
when(expr.expression.inferType(program).typeOrElse(DataType.STRUCT)) { when(typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> { DataType.UBYTE -> {
when(expr.type) { when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {} DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02) if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
@ -1010,24 +1011,16 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
} }
DataType.BYTE -> { DataType.BYTE -> {
when(expr.type) { when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {} DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> asmgen.signExtendStackLsb(DataType.BYTE)
// sign extend
asmgen.out("""
lda P8ESTACK_LO+1,x
ora #$7f
bmi +
lda #0
+ sta P8ESTACK_HI+1,x""")
}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_b2float") DataType.FLOAT -> asmgen.out(" jsr floats.stack_b2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype") in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
} }
} }
DataType.UWORD -> { DataType.UWORD -> {
when(expr.type) { when(typecast.type) {
DataType.BYTE, DataType.UBYTE -> {} DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {} DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_uw2float") DataType.FLOAT -> asmgen.out(" jsr floats.stack_uw2float")
@ -1036,7 +1029,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
} }
DataType.WORD -> { DataType.WORD -> {
when(expr.type) { when(typecast.type) {
DataType.BYTE, DataType.UBYTE -> {} DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {} DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr floats.stack_w2float") DataType.FLOAT -> asmgen.out(" jsr floats.stack_w2float")
@ -1045,7 +1038,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
} }
DataType.FLOAT -> { DataType.FLOAT -> {
when(expr.type) { when(typecast.type) {
DataType.UBYTE -> asmgen.out(" jsr floats.stack_float2uw") DataType.UBYTE -> asmgen.out(" jsr floats.stack_float2uw")
DataType.BYTE -> asmgen.out(" jsr floats.stack_float2w") DataType.BYTE -> asmgen.out(" jsr floats.stack_float2w")
DataType.UWORD -> asmgen.out(" jsr floats.stack_float2uw") DataType.UWORD -> asmgen.out(" jsr floats.stack_float2uw")
@ -1055,6 +1048,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
else -> throw AssemblyError("weird type") 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") in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
} }

View File

@ -610,8 +610,8 @@ $endLabel""")
} }
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) { private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) {
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), variable=stmt.loopVar) 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).adjustDataTypeToTarget(target) val src = AsmAssignSource.fromAstSource(range.from, program, asmgen).adjustSignedUnsigned(target)
val assign = AsmAssignment(src, target, false, range.position) val assign = AsmAssignment(src, target, false, range.position)
asmgen.translateNormalAssignment(assign) asmgen.translateNormalAssignment(assign)
} }

View File

@ -1,6 +1,7 @@
package prog8.compiler.target.c64.codegen package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* 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 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 // output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values! // 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 sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam() val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
if(saveX) if(saveX)
asmgen.saveRegister(CpuRegister.X) asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine())
val subName = asmgen.asmSymbolName(stmt.target) val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) { if(stmt.args.isNotEmpty()) {
@ -57,8 +58,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} }
asmgen.out(" jsr $subName") 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) if(saveX)
asmgen.restoreRegister(CpuRegister.X) asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
} }
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) { 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)) if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible") throw AssemblyError("argument type incompatible")
val scopedParamVar = (sub.scopedname+"."+parameter.value.name).split(".") val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
val identifier = IdentifierReference(scopedParamVar, sub.position) val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, sub, variableAsmName = varName)
identifier.linkParents(value.parent) val source = AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(tgt)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, variable = identifier)
val source = AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(tgt)
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY) val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
asmgen.translateNormalAssignment(asgn) asmgen.translateNormalAssignment(asgn)
} }
@ -170,13 +175,22 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val statusflag = paramRegister.statusflag val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair val register = paramRegister.registerOrPair
val stack = paramRegister.stack 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 { when {
stack -> { stack -> {
// push arg onto the stack // push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack) // note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value) asmgen.translateExpression(value)
if(requiredDt!=valueDt)
asmgen.signExtendStackLsb(valueDt)
} }
statusflag!=null -> { statusflag!=null -> {
if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) { if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr // 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 // 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 -> { else -> {
// via register or register pair // via register or register pair
val target = AsmAssignTarget.fromRegisters(register!!, program, asmgen) val target = AsmAssignTarget.fromRegisters(register!!, sub, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) { if(requiredDt largerThan valueDt) {
val addr = AddressOf(value as IdentifierReference, Position.DUMMY) // we need to sign extend the source, do this via temporary word variable
AsmAssignSource.fromAstSource(addr, program).adjustDataTypeToTarget(target) val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
} else { val scratchTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UBYTE, sub, scratchVar)
AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(target) 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))
} }
} }
} }

View File

@ -15,6 +15,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
val targetIdent = stmt.target.identifier val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed val targetArrayIdx = stmt.target.arrayindexed
val scope = stmt.definingSubroutine()
when { when {
targetIdent!=null -> { targetIdent!=null -> {
val what = asmgen.asmVariableName(targetIdent) val what = asmgen.asmVariableName(targetIdent)
@ -97,7 +98,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
} }
else -> { else -> {
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A) asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X) asmgen.saveRegister(CpuRegister.X, false, scope)
asmgen.out(" tax") asmgen.out(" tax")
when(elementDt) { when(elementDt) {
in ByteDatatypes -> { in ByteDatatypes -> {
@ -125,7 +126,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
} }
else -> throw AssemblyError("weird array elt dt") else -> throw AssemblyError("weird array elt dt")
} }
asmgen.restoreRegister(CpuRegister.X) asmgen.restoreRegister(CpuRegister.X, false)
} }
} }
} }

View File

@ -1,11 +1,13 @@
package prog8.compiler.target.c64.codegen.assignment package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.INameScope
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.Subroutine
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.AsmGen import prog8.compiler.target.c64.codegen.AsmGen
@ -29,10 +31,11 @@ internal enum class SourceStorageKind {
} }
internal class AsmAssignTarget(val kind: TargetStorageKind, internal class AsmAssignTarget(val kind: TargetStorageKind,
program: Program, private val program: Program,
asmgen: AsmGen, private val asmgen: AsmGen,
val datatype: DataType, val datatype: DataType,
val variable: IdentifierReference? = null, val scope: Subroutine?,
private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null, val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryWrite? = null, val memory: DirectMemoryWrite? = null,
val register: RegisterOrPair? = null, val register: RegisterOrPair? = null,
@ -41,19 +44,15 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
{ {
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0} val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() } val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
val vardecl by lazy { variable!!.targetVarDecl(program.namespace)!! } val asmVarname: String
val asmVarname by lazy { get() = if(array==null)
if(variable!=null) variableAsmName!!
asmgen.asmVariableName(variable)
else else
asmgen.asmVariableName(array!!.identifier) asmgen.asmVariableName(array.identifier)
}
lateinit var origAssign: AsmAssignment lateinit var origAssign: AsmAssignment
init { init {
if(variable!=null && vardecl.type == VarDeclType.CONST)
throw AssemblyError("can't assign to a constant")
if(register!=null && datatype !in IntegerDatatypes) if(register!=null && datatype !in IntegerDatatypes)
throw AssemblyError("register must be integer type") throw AssemblyError("register must be integer type")
} }
@ -62,29 +61,30 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) { fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
val dt = inferType(program, assign).typeOrElse(DataType.STRUCT) val dt = inferType(program, assign).typeOrElse(DataType.STRUCT)
when { when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, variable=identifier, 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, array = arrayindexed, origAstTarget = this) arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, memory = memoryAddress, origAstTarget = this) memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine(), memory = memoryAddress, origAstTarget = this)
else -> throw AssemblyError("weird target") 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) { when(registers) {
RegisterOrPair.A, RegisterOrPair.A,
RegisterOrPair.X, 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.AX,
RegisterOrPair.AY, 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, internal class AsmAssignSource(val kind: SourceStorageKind,
private val program: Program, private val program: Program,
private val asmgen: AsmGen,
val datatype: DataType, val datatype: DataType,
val variable: IdentifierReference? = null, private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null, val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryRead? = null, val memory: DirectMemoryRead? = null,
val register: CpuRegister? = null, val register: CpuRegister? = null,
@ -94,51 +94,70 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
{ {
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0} val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() } val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! }
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.identifier)
companion object { companion object {
fun fromAstSource(value: Expression, program: Program): AsmAssignSource { fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
val cv = value.constValue(program) val cv = value.constValue(program)
if(cv!=null) 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) { 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 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 ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> { is IdentifierReference -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT) 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 -> { is DirectMemoryRead -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, DataType.UBYTE, memory = value) AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
} }
is ArrayIndexedExpression -> { is ArrayIndexedExpression -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT) val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.ARRAY, program, dt, array = value) AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
} }
else -> { 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) 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) = fun withAdjustedDt(newType: DataType) =
AsmAssignSource(kind, program, newType, variable, array, memory, register, number, expression) AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
fun adjustDataTypeToTarget(target: AsmAssignTarget): AsmAssignSource { fun adjustSignedUnsigned(target: AsmAssignTarget): AsmAssignSource {
// allow some signed/unsigned relaxations // allow some signed/unsigned relaxations
if(target.datatype!=datatype) { if(target.datatype!=datatype) {
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) { if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
@ -160,6 +179,9 @@ internal class AsmAssignment(val source: AsmAssignSource,
init { init {
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY)) 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"
}
} }
} }

View File

@ -18,7 +18,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
fun translate(assignment: Assignment) { fun translate(assignment: Assignment) {
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen) 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) val assign = AsmAssignment(source, target, assignment.isAugmentable, assignment.position)
target.origAssign = assign target.origAssign = assign
@ -34,7 +34,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
SourceStorageKind.LITERALNUMBER -> { SourceStorageKind.LITERALNUMBER -> {
// simple case: assign a constant number // simple case: assign a constant number
val num = assign.source.number!!.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.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toShort())
DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt()) DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt())
DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble()) DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
@ -43,12 +43,16 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
SourceStorageKind.VARIABLE -> { SourceStorageKind.VARIABLE -> {
// simple case: assign from another variable // simple case: assign from another variable
val variable = assign.source.variable!! val variable = assign.source.asmVarname!!
when (assign.source.datatype) { when (assign.target.datatype) {
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable) DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
DataType.UWORD, DataType.WORD -> assignVariableWord(assign.target, variable) DataType.UWORD, DataType.WORD -> assignVariableWord(assign.target, variable)
DataType.FLOAT -> assignVariableFloat(assign.target, variable) DataType.FLOAT -> assignVariableFloat(assign.target, variable)
in PassByReferenceDatatypes -> assignAddressOf(assign.target, variable) DataType.STR -> assignVariableString(assign.target, variable)
in PassByReferenceDatatypes -> {
// TODO what about when the name is a struct? name.firstStructVarName(program.namespace)
assignAddressOf(assign.target, variable)
}
else -> throw AssemblyError("unsupported assignment target type ${assign.target.datatype}") else -> throw AssemblyError("unsupported assignment target type ${assign.target.datatype}")
} }
} }
@ -116,29 +120,45 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
SourceStorageKind.EXPRESSION -> { SourceStorageKind.EXPRESSION -> {
val value = assign.source.expression!! val value = assign.source.expression!!
when(value) { when(value) {
is AddressOf -> assignAddressOf(assign.target, value.identifier) 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 NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
is IdentifierReference -> throw AssemblyError("source kind should have been variable") is IdentifierReference -> throw AssemblyError("source kind should have been variable")
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array") is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory") is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, assign) is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, assign)
// is FunctionCall -> { is FunctionCall -> {
// if (assign.target.kind == TargetStorageKind.STACK) { if(value.target.targetSubroutine(program.namespace)?.isAsmSubroutine==true) {
// asmgen.translateExpression(value) // handle asmsub functioncalls specifically, without shoving stuff on the estack
// assignStackValue(assign.target) val sub = value.target.targetSubroutine(program.namespace)!!
// } else { val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any { it.statusflag != null }
// val functionName = value.target.nameInSource.last() asmgen.translateFunctionCall(value, preserveStatusRegisterAfterCall)
// val builtinFunc = BuiltinFunctions[functionName] when((sub.asmReturnvaluesRegisters.single { it.registerOrPair!=null }).registerOrPair) {
// if (builtinFunc != null) { RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
// println("!!!!BUILTIN-FUNCCALL target=${assign.target.kind} $value") // TODO optimize certain functions? RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
// } RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
// asmgen.translateExpression(value) RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
// assignStackValue(assign.target) 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 -> { else -> {
// everything else just evaluate via the stack. // everything else just evaluate via the stack.
asmgen.translateExpression(value) asmgen.translateExpression(value)
if(assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
assignStackValue(assign.target) assignStackValue(assign.target)
} }
} }
@ -156,9 +176,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val valueDt = value.inferType(program).typeOrElse(DataType.STRUCT) val valueDt = value.inferType(program).typeOrElse(DataType.STRUCT)
when(value) { when(value) {
is IdentifierReference -> { is IdentifierReference -> {
if (valueDt == DataType.UBYTE || valueDt == DataType.BYTE) { if(targetDt in WordDatatypes) {
if(targetDt in WordDatatypes) { if(valueDt==DataType.UBYTE) {
assignVariableByteIntoWord(target, value, valueDt) assignVariableUByteIntoWord(target, value)
return
}
if(valueDt==DataType.BYTE) {
assignVariableByteIntoWord(target, value)
return return
} }
} }
@ -221,6 +245,18 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
jsr floats.pop_float 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}") else -> throw AssemblyError("weird target variable type ${target.datatype}")
} }
} }
@ -319,9 +355,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
private fun assignAddressOf(target: AsmAssignTarget, name: IdentifierReference) { private fun assignAddressOf(target: AsmAssignTarget, sourceName: String) {
val sourceName = name.firstStructVarName(program.namespace) ?: asmgen.fixNameSymbols(name.nameInSource.joinToString("."))
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
asmgen.out(""" asmgen.out("""
@ -346,19 +380,54 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
val srcname = asmgen.asmVariableName(name)
asmgen.out(""" asmgen.out("""
lda #<$srcname lda #<$sourceName
sta P8ESTACK_LO,x sta P8ESTACK_LO,x
lda #>$srcname lda #>$sourceName
sta P8ESTACK_HI,x sta P8ESTACK_HI,x
dex""") dex""")
} }
} }
} }
private fun assignVariableWord(target: AsmAssignTarget, variable: IdentifierReference) { private fun assignVariableString(target: AsmAssignTarget, sourceName: String) {
val sourceName = asmgen.asmVariableName(variable) 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) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
asmgen.out(""" asmgen.out("""
@ -461,8 +530,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
private fun assignVariableFloat(target: AsmAssignTarget, variable: IdentifierReference) { private fun assignVariableFloat(target: AsmAssignTarget, sourceName: String) {
val sourceName = asmgen.asmVariableName(variable)
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
asmgen.out(""" asmgen.out("""
@ -496,8 +564,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
private fun assignVariableByte(target: AsmAssignTarget, variable: IdentifierReference) { private fun assignVariableByte(target: AsmAssignTarget, sourceName: String) {
val sourceName = asmgen.asmVariableName(variable)
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
asmgen.out(""" asmgen.out("""
@ -546,10 +613,68 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
private fun assignVariableByteIntoWord(wordtarget: AsmAssignTarget, bytevar: IdentifierReference, valueDt: DataType) { private fun assignVariableByteIntoWord(wordtarget: AsmAssignTarget, bytevar: IdentifierReference) {
if(valueDt == DataType.BYTE) val sourceName = asmgen.asmVariableName(bytevar)
TODO("sign extend byte to word") 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
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) val sourceName = asmgen.asmVariableName(bytevar)
when(wordtarget.kind) { when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
@ -589,13 +714,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
asmgen.out(""" asmgen.out("""
lda #$sourceName lda $sourceName
sta P8ESTACK_LO,x sta P8ESTACK_LO,x
lda #0 lda #0
sta P8ESTACK_HI,x sta P8ESTACK_HI,x
dex""") dex""")
} }
else -> throw AssemblyError("other types aren't word") else -> throw AssemblyError("target type isn't word")
} }
} }
@ -628,9 +753,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" ldy ${asmgen.asmVariableName(index)} | sta ${target.asmVarname},y") asmgen.out(" ldy ${asmgen.asmVariableName(index)} | sta ${target.asmVarname},y")
} }
else -> { else -> {
asmgen.saveRegister(register) asmgen.saveRegister(register, false, target.scope)
asmgen.translateExpression(index) asmgen.translateExpression(index)
asmgen.restoreRegister(register) asmgen.restoreRegister(register, false)
when (register) { when (register) {
CpuRegister.A -> asmgen.out(" sta P8ZP_SCRATCH_B1") CpuRegister.A -> asmgen.out(" sta P8ZP_SCRATCH_B1")
CpuRegister.X -> asmgen.out(" stx P8ZP_SCRATCH_B1") CpuRegister.X -> asmgen.out(" stx P8ZP_SCRATCH_B1")
@ -684,6 +809,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) { private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
@ -1079,9 +1252,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.storeByteIntoPointer(addressExpr, null) asmgen.storeByteIntoPointer(addressExpr, null)
} }
else -> { else -> {
asmgen.saveRegister(register) asmgen.saveRegister(register, false, memoryAddress.definingSubroutine())
asmgen.translateExpression(addressExpr) asmgen.translateExpression(addressExpr)
asmgen.restoreRegister(CpuRegister.A) asmgen.restoreRegister(CpuRegister.A, false)
asmgen.out(""" asmgen.out("""
inx inx
ldy P8ESTACK_LO,x ldy P8ESTACK_LO,x

View File

@ -1,8 +1,10 @@
package prog8.compiler.target.c64.codegen.assignment package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.INameScope
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType import prog8.compiler.target.CpuType
@ -137,13 +139,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
DataType.FLOAT -> { DataType.FLOAT -> {
when { when {
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble()) 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) ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident, target.scope)
value is TypecastExpression -> { value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return 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}") else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
@ -222,123 +224,92 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_byte_value_to_memory(pointervar: IdentifierReference, operator: String, value: Expression) { 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 println("warning: slow stack evaluation used (3): @(${pointervar.nameInSource.last()}) $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value) asmgen.translateExpression(value)
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
when (operator) { when (operator) {
// note: ** (power) operator requires floats. // note: ** (power) operator requires floats.
"+" -> { "+" -> asmgen.out(" clc | adc P8ESTACK_LO+1,x")
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar) "-" -> asmgen.out(" sec | sbc P8ESTACK_LO+1,x")
asmgen.out(" clc | adc P8ESTACK_LO+1,x") "*" -> asmgen.out(" pha | lda P8ESTACK_LO+1,x | tay | pla | jsr math.multiply_bytes | ldy #0")
if(ptrOnZp) "/" -> asmgen.out(" pha | lda P8ESTACK_LO+1,x | tay | pla | jsr math.divmod_ub_asm | tya | ldy #0")
asmgen.out(" sta ($sourceName),y") "%" -> asmgen.out(" pha | lda P8ESTACK_LO+1,x | tay | pla | jsr math.divmod_ub_asm | ldy #0")
else "<<" -> {
asmgen.out(" sta (P8ZP_SCRATCH_W1),y") 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("""
asmgen.out(" sec | sbc P8ESTACK_LO+1,x") pha
if(ptrOnZp) lda P8ESTACK_LO+1,x
asmgen.out(" sta ($sourceName),y") bne +
else pla
asmgen.out(" sta (P8ZP_SCRATCH_W1),y") rts
} + tay
"*" -> { pla
TODO("mul mem byte")// asmgen.out(" jsr prog8_lib.mul_byte") - lsr a
} dey
"/" -> TODO("div mem byte")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b") bne -
"%" -> { +""")
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(" 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") 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") asmgen.out(" inx")
} }
private fun inplaceModification_byte_variable_to_memory(pointervar: IdentifierReference, operator: String, value: IdentifierReference) { private fun inplaceModification_byte_variable_to_memory(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
val otherName = asmgen.asmVariableName(value) val otherName = asmgen.asmVariableName(value)
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
when (operator) { when (operator) {
// note: ** (power) operator requires floats. // note: ** (power) operator requires floats.
"+" -> { "+" -> asmgen.out(" clc | adc $otherName")
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar) "-" -> asmgen.out(" sec | sbc $otherName")
asmgen.out(" clc | adc $otherName") "*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes | ldy #0")
if(ptrOnZp) "/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya | ldy #0")
asmgen.out(" sta ($sourceName),y") "%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | ldy #0")
else "<<" -> {
asmgen.out(" sta (P8ZP_SCRATCH_W1),y") asmgen.out("""
ldy $otherName
beq +
- asl a
dey
bne -
+""")
} }
"-" -> { ">>" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar) asmgen.out("""
asmgen.out(" sec | sbc $otherName") ldy $otherName
if(ptrOnZp) beq +
asmgen.out(" sta ($sourceName),y") - lsr a
else dey
asmgen.out(" sta (P8ZP_SCRATCH_W1),y") bne -
} +""")
"*" -> {
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(" and $otherName")
"^" -> asmgen.out(" eor $otherName")
"|" -> asmgen.out(" ora $otherName")
else -> throw AssemblyError("invalid operator for in-place modification $operator") 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) { 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") asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
} }
"*" -> { "*" -> {
if(value in asmgen.optimizedByteMultiplications) { val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
TODO("optimized mem mul ubyte litval $value") if(value in asmgen.optimizedByteMultiplications)
} else { asmgen.out(" jsr math.mul_byte_${value}")
TODO("mem mul ubyte litval $value") else
// asmgen.out(" jsr prog8_lib.mul_byte") 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) if(value==0)
throw AssemblyError("division by zero") throw AssemblyError("division by zero")
TODO("mem div byte litval") asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya | ldy #0")
// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b") if(ptrOnZp)
asmgen.out(" sta ($sourceName),y")
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
} }
"%" -> { "%" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0) if(value==0)
throw AssemblyError("division by zero") throw AssemblyError("division by zero")
TODO("mem byte remainder litval") asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | ldy #0")
// if(types==DataType.BYTE) if(ptrOnZp)
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead") asmgen.out(" sta ($sourceName),y")
// asmgen.out(" jsr prog8_lib.remainder_ub") else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
} }
"<<" -> { "<<" -> {
if (value > 0) { if (value > 0) {
@ -439,19 +419,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// note: ** (power) operator requires floats. // note: ** (power) operator requires floats.
"+" -> asmgen.out(" lda $name | clc | adc P8ESTACK_LO+1,x | sta $name") "+" -> asmgen.out(" lda $name | clc | adc P8ESTACK_LO+1,x | sta $name")
"-" -> asmgen.out(" lda $name | sec | sbc P8ESTACK_LO+1,x | sta $name") "-" -> asmgen.out(" lda $name | sec | sbc P8ESTACK_LO+1,x | sta $name")
"*" -> { "*" -> asmgen.out(" lda P8ESTACK_LO+1,x | ldy $name | jsr math.multiply_bytes | sta $name")
TODO("var mul byte expr")
// check optimizedByteMultiplications
// asmgen.out(" jsr prog8_lib.mul_byte")
}
"/" -> { "/" -> {
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(dt==DataType.BYTE)
// if(types==DataType.BYTE) throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
// 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.out(" jsr prog8_lib.remainder_ub")
} }
"<<" -> { "<<" -> {
asmgen.translateExpression(value) asmgen.translateExpression(value)
@ -571,33 +549,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"+" -> asmgen.out(" lda $name | clc | adc #$value | sta $name") "+" -> asmgen.out(" lda $name | clc | adc #$value | sta $name")
"-" -> asmgen.out(" lda $name | sec | sbc #$value | sta $name") "-" -> asmgen.out(" lda $name | sec | sbc #$value | sta $name")
"*" -> { "*" -> {
if(dt == DataType.UBYTE) { if(value in asmgen.optimizedByteMultiplications)
if(value in asmgen.optimizedByteMultiplications) { asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name") else
} else { asmgen.out(" lda $name | ldy #$value | jsr math.multiply_bytes | sta $name")
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 (dt == DataType.UBYTE) { if (dt == DataType.UBYTE)
asmgen.out(""" asmgen.out(" lda $name | ldy #$value | jsr math.divmod_ub_asm | sty $name")
lda $name else
ldy #$value asmgen.out(" lda $name | ldy #$value | jsr math.divmod_b_asm | sty $name")
jsr math.divmod_ub_asm
sty $name
""")
} else {
TODO("var BYTE div litval")
}
} }
"%" -> { "%" -> {
if(dt==DataType.BYTE) if(dt==DataType.BYTE)
@ -644,7 +605,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sec sec
sbc P8ZP_SCRATCH_B1 sbc P8ZP_SCRATCH_B1
sta $name""") sta $name""")
// TODO: more operators // TODO: tuned code for more operators
} }
else -> { else -> {
inplaceModification_byte_value_to_variable(name, dt, operator, memread); inplaceModification_byte_value_to_variable(name, dt, operator, memread);
@ -675,7 +636,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
bcc + bcc +
dec $name+1 dec $name+1
+""") +""")
// TODO: more operators // TODO: tuned code for more operators
} }
else -> { else -> {
inplaceModification_word_value_to_variable(name, dt, operator, memread); inplaceModification_word_value_to_variable(name, dt, operator, memread);
@ -1072,6 +1033,68 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.translateExpression(value) asmgen.translateExpression(value)
val valueDt = value.inferType(program).typeOrElse(DataType.STRUCT) 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) { when(valueDt) {
in ByteDatatypes -> { in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that // the other variable is a BYTE type so optimize for that
@ -1125,9 +1148,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc P8ZP_SCRATCH_B1 sbc P8ZP_SCRATCH_B1
sta $name+1""") sta $name+1""")
} }
"*" -> TODO("mul (u)word (u)byte") "*" -> {
"/" -> TODO("div (u)word (u)byte") // stack contains (u) byte value, sign extend that and proceed with regular 16 bit operation
"%" -> TODO("(u)word remainder (u)byte") 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.translateExpression(value)
asmgen.out(""" asmgen.out("""
@ -1166,9 +1201,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""") +""")
} }
} }
"&" -> TODO("bitand (u)word (u)byte") "&" -> TODO("bitand (u)word (u)byte on stack")
"^" -> TODO("bitxor (u)word (u)byte") "^" -> TODO("bitxor (u)word (u)byte on stack")
"|" -> TODO("bitor (u)word (u)byte") "|" -> TODO("bitor (u)word (u)byte on stack")
else -> throw AssemblyError("invalid operator for in-place modification $operator") else -> throw AssemblyError("invalid operator for in-place modification $operator")
} }
} }
@ -1178,65 +1213,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// note: ** (power) operator requires floats. // 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 | 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 $name | sec | sbc P8ESTACK_LO+1,x | sta $name | lda $name+1 | sbc P8ESTACK_HI+1,x | sta $name+1")
"*" -> { "*" -> multiplyWord()
asmgen.out(""" "/" -> divideWord()
lda P8ESTACK_LO+1,x "%" -> remainderWord()
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
""")
}
"<<", ">>" -> throw AssemblyError("shift by a word value not supported, max is a byte") "<<", ">>" -> 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 | 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") "^" -> asmgen.out(" lda $name | eor P8ESTACK_LO+1,x | sta $name | lda $name+1 | eor P8ESTACK_HI+1,x | sta $name+1")
@ -1252,13 +1231,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" inx") 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, // this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow). // 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 println("warning: slow stack evaluation used (2): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value) asmgen.translateExpression(value)
asmgen.out(" jsr floats.pop_float_fac1") asmgen.out(" jsr floats.pop_float_fac1")
asmgen.saveRegister(CpuRegister.X) asmgen.saveRegister(CpuRegister.X, false, scope)
when (operator) { when (operator) {
"**" -> { "**" -> {
asmgen.out(""" asmgen.out("""
@ -1303,16 +1282,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ldy #>$name ldy #>$name
jsr floats.MOVMF 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 val valueDt = ident.targetVarDecl(program.namespace)!!.datatype
if(valueDt != DataType.FLOAT) if(valueDt != DataType.FLOAT)
throw AssemblyError("float variable expected") throw AssemblyError("float variable expected")
val otherName = asmgen.asmVariableName(ident) val otherName = asmgen.asmVariableName(ident)
asmgen.saveRegister(CpuRegister.X) asmgen.saveRegister(CpuRegister.X, false, scope)
when (operator) { when (operator) {
"**" -> { "**" -> {
asmgen.out(""" asmgen.out("""
@ -1372,12 +1351,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ldy #>$name ldy #>$name
jsr floats.MOVMF 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) val constValueName = asmgen.getFloatAsmConst(value)
asmgen.saveRegister(CpuRegister.X) asmgen.saveRegister(CpuRegister.X, false, scope)
when (operator) { when (operator) {
"**" -> { "**" -> {
asmgen.out(""" asmgen.out("""
@ -1444,7 +1423,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ldy #>$name ldy #>$name
jsr floats.MOVMF jsr floats.MOVMF
""") """)
asmgen.restoreRegister(CpuRegister.X) asmgen.restoreRegister(CpuRegister.X, false)
} }
private fun inplaceCast(target: AsmAssignTarget, cast: TypecastExpression, position: Position) { private fun inplaceCast(target: AsmAssignTarget, cast: TypecastExpression, position: Position) {

View File

@ -285,9 +285,9 @@ private fun builtinStrlen(args: List<Expression>, position: Position, program: P
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position) return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace) val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
if(vardecl!=null) { 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) 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) return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
} }
} }

View File

@ -0,0 +1,89 @@
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, 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
}
}
}

View File

@ -1,15 +1,11 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification 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.abs
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.pow import kotlin.math.pow
@ -279,80 +275,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
return noModifications 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> { override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
if(functionCall.target.nameInSource == listOf("lsb")) { if(functionCall.target.nameInSource == listOf("lsb")) {
val arg = functionCall.args[0] val arg = functionCall.args[0]

View File

@ -53,3 +53,9 @@ internal fun Program.simplifyExpressions() : Int {
opti.visit(this) opti.visit(this)
return opti.applyModifications() return opti.applyModifications()
} }
internal fun Program.splitBinaryExpressions() : Int {
val opti = BinExprSplitter(this)
opti.visit(this)
return opti.applyModifications()
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -96,6 +96,12 @@ when compiled an ran on a C-64 you get this:
:align: center :align: center
:alt: result when run on C-64 :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 Design principles and features

View File

@ -260,6 +260,11 @@ 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`` can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance. 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:** **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. 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:: For instance to reference the first 5 rows of the Commodore 64's screen matrix as an array, you can define::
@ -287,7 +292,7 @@ This @-prefix can also be used for character byte values.
You can concatenate two string literals using '+' (not very useful though) or repeat 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 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 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 string1 = "first part" + "second part"
str string2 = "hello!" * 10 str string2 = "hello!" * 10
@ -296,7 +301,13 @@ large enough to contain the new value::
string1 = "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.
.. note::
Strings and uwords (=memory address) can often be interchanged. 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 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 address of the string. You can pass a memory address to assembly functions

View File

@ -78,7 +78,7 @@ Directives
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything, - 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. 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. 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 This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code). 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 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 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 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. 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. 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. Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines 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. (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 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. directly from the language, because only single value assignments are possible.
You can still call the subroutine and not store the results. 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 **There is an exception:** if there's just one return value in a register, and one or more others that are returned
appropriately. Don't forget to save/restore the registers if required. 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 Subroutine definitions

View File

@ -3,7 +3,6 @@ TODO
==== ====
- get rid of all other TODO's in the code ;-) - 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 - 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 '_' - 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) - 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: - further optimize assignment codegeneration, such as the following:
- binexpr splitting (beware self-referencing expressions and asm code ballooning though) - 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? - can such parameter passing to subroutines be optimized to avoid copying?
- more optimizations on the language AST level - more optimizations on the language AST level
- more optimizations on the final assembly source level - more optimizations on the final assembly source level

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,12 +3,14 @@
%target cx16 %target cx16
%zeropage basicsafe %zeropage basicsafe
main { main {
sub start() { sub start() {
void cx16.screen_set_mode($80) void cx16.screen_set_mode($80)
cx16.r0=0 cx16.r0=0
void cx16.screen_set_mode(0)
cx16.FB_init() cx16.FB_init()
cx16.GRAPH_init() cx16.GRAPH_init()

View File

@ -10,38 +10,6 @@
; You load it with LOAD "diskdir-sys50000",8,1 ; You load it with LOAD "diskdir-sys50000",8,1
; and then call it with SYS 50000. ; and then call it with SYS 50000.
main { ; The only difference with diskdir.p8 is the directives that make this load at 50000.
sub start() {
txt.print("directory of disk drive #8:\n\n")
diskdir(8)
}
sub diskdir(ubyte drivenumber) { %import diskdir
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
}
}

View File

@ -1,10 +1,10 @@
%target c64
%import textio %import textio
%import syslib %import syslib
%option no_sysinit %option no_sysinit
%zeropage basicsafe %zeropage basicsafe
; This example shows the directory contents of disk drive 8. ; This example shows the directory contents of disk drive 8.
; Note: this program is compatible with C64 and CX16.
main { main {
sub start() { sub start() {
@ -15,15 +15,20 @@ main {
sub diskdir(ubyte drivenumber) { sub diskdir(ubyte drivenumber) {
c64.SETNAM(1, "$") c64.SETNAM(1, "$")
c64.SETLFS(1, drivenumber, 0) c64.SETLFS(1, drivenumber, 0)
c64.OPEN() ; open 1,8,0,"$" void c64.OPEN() ; open 1,8,0,"$"
c64.CHKIN(1) ; use #1 as input channel if_cs
goto io_error
void c64.CHKIN(1) ; use #1 as input channel
if_cs
goto io_error
repeat 4 { repeat 4 {
void c64.CHRIN() ; skip the 4 prologue bytes void c64.CHRIN() ; skip the 4 prologue bytes
} }
; while not key pressed / EOF encountered, read data. ; while not key pressed / EOF encountered, read data.
while not (@($c6) | c64.STATUS) { ubyte status = c64.READST()
while not status {
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN())) txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
txt.chrout(' ') txt.chrout(' ')
ubyte @zp char ubyte @zp char
@ -32,12 +37,23 @@ main {
txt.chrout(char) txt.chrout(char)
} until char==0 } until char==0
txt.chrout('\n') txt.chrout('\n')
repeat 2 { void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN() ; skip 2 bytes void c64.CHRIN()
} status = c64.READST()
void c64.STOP()
if_nz
break
} }
c64.CLOSE(1) io_error:
status = c64.READST()
c64.CLRCHN() ; restore default i/o devices 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')
}
} }
} }

View File

@ -9,14 +9,14 @@
; Note: this program is compatible with C64 and CX16. ; Note: this program is compatible with C64 and CX16.
main { main {
const ubyte width = 255 const uword width = 320
const ubyte height = 200 const ubyte height = 200
const ubyte max_iter = 16 const ubyte max_iter = 16
sub start() { sub start() {
graphics.enable_bitmap_mode() graphics.enable_bitmap_mode()
ubyte pixelx uword pixelx
ubyte pixely ubyte pixely
for pixely in 0 to height-1 { for pixely in 0 to height-1 {

View File

@ -30,7 +30,7 @@ main {
txt.print("es") txt.print("es")
txt.print(" left.\nWhat is your next guess? ") txt.print(" left.\nWhat is your next guess? ")
void txt.input_chars(input) void txt.input_chars(input)
ubyte guess = lsb(conv.str2uword(input)) ubyte guess = conv.str2ubyte(input)
if guess==secretnumber { if guess==secretnumber {
ending(true) ending(true)

View File

@ -249,8 +249,10 @@ waitkey:
txt.print("────────────────────────") txt.print("────────────────────────")
c64.CHROUT('K') c64.CHROUT('K')
while c64.GETIN()!=133 { ubyte key = 0
while key!=133 {
; endless loop until user presses F1 to restart the game ; endless loop until user presses F1 to restart the game
key = c64.GETIN()
} }
} }

View File

@ -6,37 +6,8 @@
main { 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() { 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')
c1 = [99,88,77]
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()
} }
asmsub testX() { asmsub testX() {
@ -53,5 +24,7 @@ main {
_saveX .byte 0 _saveX .byte 0
}} }}
} }
} }

View File

@ -18,7 +18,6 @@ main {
&str ms1 = $c000 &str ms1 = $c000
byte[4] barray byte[4] barray
ubyte[4] ubarray ubyte[4] ubarray
word[4] warray word[4] warray

941
examples/textelite.p8 Normal file
View File

@ -0,0 +1,941 @@
%import textio
%import conv
%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.
; TODO save/load game function
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.0 ---\n")
galaxy.init(1)
galaxy.travel_to(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 input = "??????????"
ubyte num_chars
sub do_load() {
txt.print("\nTODO LOAD\n")
}
sub do_save() {
txt.print("\nTODO SAVE\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(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.nextgalaxy()
galaxy.travel_to(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(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"]
str[3] unitnames = ["t", "kg", "g"]
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])
txt.print(unitnames[units[ci]])
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 system) {
init(number)
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(current_closest_pi)
else
travel_to(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(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++
}
if pairs0[x+1] != '.' {
name[nx] = pairs0[x+1]
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)
; TODO recursive call... should give error message... but hey since it's not doing that here now, lets exploit it
recursive_soup() ; RECURSIVE CALL
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
}}
}
}

View File

@ -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 | 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 | 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 = 'and' EOL? right = expression
| left = expression EOL? bop = 'or' EOL? right = expression | left = expression EOL? bop = 'or' EOL? right = expression