Compare commits

...

46 Commits
v4.5 ... v4.6

Author SHA1 Message Date
2ba6c9ccbe textelite 1.1 finalize load/save, add it to examplesd disk 2020-10-20 21:49:06 +02:00
3eaf111e7d added 'slowwarn' cli option 2020-10-20 21:38:37 +02:00
30da26b9a9 tackling problem of invalid reuse of auto indexer var 2020-10-20 21:23:43 +02:00
e35ad0cc8f code cleanups 2020-10-20 17:54:16 +02:00
1a36302cf1 rest of optimizations following simplification of array indexer 2020-10-19 23:57:00 +02:00
82a28bb555 extra attempt to simplify add and subtract with negative numbers 2020-10-19 23:01:32 +02:00
c1ce0be451 slightly optimize expression code for most common cases +/- 1 , */div 2 2020-10-19 22:50:38 +02:00
c0a5f8fef0 removed double mul code 2020-10-19 21:32:44 +02:00
702cf304d0 implemented missing swap() operations 2020-10-19 21:26:11 +02:00
4dee8b6048 remove superfluous value eval 2020-10-19 02:38:26 +02:00
ec665e0cc1 fixed incorrect removal of certain assignments that are NOT double 2020-10-19 02:16:23 +02:00
aec3b82476 fixed bitshifting by more than the number of bits in the value 2020-10-19 02:05:01 +02:00
e83796b5b9 fixed bit shifting by 0. optimized bitshifting code. 2020-10-18 17:12:52 +02:00
8eb69d6eda vardecl with initializer expression are now optimized again (unless floats) 2020-10-18 16:15:05 +02:00
74b5124a42 removed restriction on array indexer expression again from docs and code... :) 2020-10-18 14:05:26 +02:00
b9706a180b fix array indexer bug 2020-10-18 13:49:53 +02:00
8aeb8a9bb7 reintroduce expressions for array indexing 2020-10-18 13:33:11 +02:00
8f2e166a22 annotated some high prio todos 2020-10-17 22:57:54 +02:00
fdd91170dc allow simple binary expressions as array indexing too, but not more 2020-10-17 22:43:35 +02:00
c40ddb061b example adjustments 2020-10-17 21:00:59 +02:00
353d6cfc55 doc about array index restriction 2020-10-17 20:35:36 +02:00
f37564c49c fixed 2020-10-17 19:59:48 +02:00
157484d94b adapted p8 code to restricted array indexing 2020-10-17 19:57:55 +02:00
7626c9fff7 only allow array indexing via a number, or a variable (eliminate complex expression calcs for array indexing, force explicit use of an index variable) 2020-10-17 19:57:55 +02:00
1f55f9fc49 removed 2 problematic ZP locations for the C64 2020-10-17 19:57:10 +02:00
2554bc7ef8 ordered the functions in the docs 2020-10-17 02:14:19 +02:00
7cb4100419 string can be compared directly (uses strcmp() automatically in asm) 2020-10-17 02:01:00 +02:00
2d3b7eb878 started making string compares use strcmp() automatically 2020-10-17 01:11:01 +02:00
4d01a78731 introduced strcmp() builtin function 2020-10-16 19:00:06 +02:00
a03e36828a fixed lines in assembly source optimizer 2020-10-16 01:48:03 +02:00
260fb65b06 making strcmp 2020-10-16 00:11:46 +02:00
9fb8526136 added conv.bin and hex string to number 2020-10-15 23:47:10 +02:00
26fc5ff5e2 preparing conv.bin and hex string to number 2020-10-15 23:10:28 +02:00
5060f0bb19 fixed assigning a memory byte from an array 2020-10-15 22:15:00 +02:00
beaf6d449b added short overview of the library modules 2020-10-15 21:30:03 +02:00
4d68b508a2 proper error if variable name is the same as its subroutine or block (that would create naming problems in the assembly code) 2020-10-15 20:48:18 +02:00
cd825e386d fix invalid address-of error when taking address of struct variable 2020-10-15 20:14:17 +02:00
095c8b2309 corrected name and added cx16logo library module for fun 2020-10-15 00:58:41 +02:00
8b6eb74c58 refactor 2020-10-14 23:43:38 +02:00
aba437e5a2 diskio load and save use kernel routines for load and save, and don't bother with SEQ files 2020-10-14 22:33:49 +02:00
efe3ed499b starting with load/save in textelite 2020-10-14 02:51:00 +02:00
5595564a1f todo strcmp 2020-10-14 01:22:43 +02:00
439761cb67 fixed C64 ZP addresses to allow disk I/O, introduced diskio library module 2020-10-14 01:17:18 +02:00
bee6c65293 fixed several bugs in the repeat assembly for loop sizes like 0 and 256 2020-10-13 21:48:15 +02:00
10145b946b invalid repeat loop code is generated... 2020-10-13 16:27:40 +02:00
ebf4b50059 reused existing CallGraph to check for recursion, which is now fixed. It's a warning too now. 2020-10-12 23:04:00 +02:00
66 changed files with 2071 additions and 1286 deletions

View File

@ -8,7 +8,7 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />

View File

@ -208,17 +208,6 @@ pop_float_fac2 .proc
jmp CONUPK
.pend
pop_float_to_indexed_var .proc
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp pop_float
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
@ -707,13 +696,12 @@ sign_f .proc
set_0_array_float .proc
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
inx
lda P8ESTACK_LO,x
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ESTACK_LO,x
adc P8ZP_SCRATCH_B1
tay
lda #0
sta (P8ZP_SCRATCH_W1),y
@ -730,13 +718,12 @@ set_0_array_float .proc
set_array_float .proc
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
inx
lda P8ESTACK_LO,x
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ESTACK_LO,x
adc P8ZP_SCRATCH_B1
adc P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
bcc +

View File

@ -33,7 +33,7 @@ graphics {
}
word @zp d = 0
ubyte positive_ix = true
word @zp dx = x2-x1
word @zp dx = x2-x1 as word
word @zp dy = y2-y1
if dx < 0 {
dx = -dx

View File

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

View File

@ -250,7 +250,7 @@ output .text "0000", $00 ; 0-terminated output buffer (to make printing ea
}
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
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)
@ -260,8 +260,7 @@ asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
}}
}
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
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)
@ -271,7 +270,7 @@ asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
}}
}
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
; 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)
@ -325,7 +324,7 @@ _result_times_10 ; (W*4 + W)*2
}}
}
asmsub str2word(str string @ AY) -> word @ AY {
asmsub str2word(str string @ AY) -> word @ AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
@ -380,4 +379,77 @@ _negative .byte 0
}}
}
asmsub hex2uword(str string @ AY) -> uword @AY {
; -- hexadecimal string with or without '$' to uword.
; string may be in petscii or c64-screencode encoding.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
sty P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'$'
beq _skip
cmp #7
bcc _add_nine
cmp #'9'
beq _calc
bcs _add_nine
_calc asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #$0f
clc
adc P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
_skip inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
_add_nine ldy #9
sty P8ZP_SCRATCH_B1
bne _calc
}}
}
asmsub bin2uword(str string @ AY) -> uword @AY {
; -- binary string with or without '%' to uword.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'%'
beq +
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
}}
}
}

View File

@ -12,9 +12,9 @@ c64 {
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
; STROUT --> use screen.print
; CLEARSCR -> use screen.clear_screen
; HOMECRSR -> use screen.plot
; STROUT --> use txt.print
; CLEARSCR -> use txt.clear_screen
; HOMECRSR -> use txt.plot
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
@ -53,7 +53,7 @@ romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
}

View File

@ -134,13 +134,16 @@ _la lda #0 ; modified
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
sub color (ubyte txtcol) {
c64.CHROUT(color_to_charcode[txtcol & 15])
txtcol &= 15
c64.CHROUT(color_to_charcode[txtcol])
}
sub color2 (ubyte txtcol, ubyte bgcol) {
c64.CHROUT(color_to_charcode[bgcol & 15])
txtcol &= 15
bgcol &= 15
c64.CHROUT(color_to_charcode[bgcol])
c64.CHROUT(1) ; switch fg and bg colors
c64.CHROUT(color_to_charcode[txtcol & 15])
c64.CHROUT(color_to_charcode[txtcol])
}
sub lowercase() {

View File

@ -0,0 +1,29 @@
%import textio
cx16logo {
sub logo_at(ubyte column, ubyte row) {
uword strptr
for strptr in logo_lines {
txt.plot(column, row)
txt.print(strptr)
row++
}
}
sub logo() {
uword strptr
for strptr in logo_lines
txt.print(strptr)
txt.chrout('\n')
}
str[] logo_lines = [
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b\n",
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b\n",
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌\n",
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132\n",
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂\n",
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b\n",
"\uf101\uf130\uf13a \uf139▎\uf100"
]
}

View File

@ -0,0 +1,163 @@
%import textio
%import syslib
; Note: this code is compatible with C64 and CX16.
diskio {
sub directory(ubyte drivenumber) -> byte {
; -- Shows the directory contents of disk drive 8-11 (provide as argument).
c64.SETNAM(1, "$")
c64.SETLFS(1, drivenumber, 0)
void c64.OPEN() ; open 1,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(1) ; use #1 as input channel
if_cs
goto io_error
repeat 4 {
void c64.CHRIN() ; skip the 4 prologue bytes
}
; while not key pressed / EOF encountered, read data.
ubyte status = c64.READST()
while not status {
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
txt.chrout(' ')
ubyte @zp char
do {
char = c64.CHRIN()
txt.chrout(char)
} until char==0
txt.chrout('\n')
void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN()
status = c64.READST()
void c64.STOP()
if_nz
break
}
io_error:
status = c64.READST()
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(1)
if status and status != 64 { ; 64=end of file
txt.print("\ni/o error, status: ")
txt.print_ub(status)
txt.chrout('\n')
return false
}
return true
}
sub status(ubyte drivenumber) {
; -- display the disk drive's current status message
c64.SETNAM(0, $0000)
c64.SETLFS(15, drivenumber, 15)
void c64.OPEN() ; open 15,8,15
if_cs
goto io_error
void c64.CHKIN(15) ; use #15 as input channel
if_cs
goto io_error
while not c64.READST()
txt.chrout(c64.CHRIN())
io_error:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(15)
}
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> byte {
c64.SETNAM(strlen(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0)
uword end_address = address + size
%asm {{
lda address
sta P8ZP_SCRATCH_W1
lda address+1
sta P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_REG
lda #<P8ZP_SCRATCH_W1
ldx end_address
ldy end_address+1
jsr c64.SAVE
php
ldx P8ZP_SCRATCH_REG
plp
}}
if_cc
return c64.READST()==0
return false
}
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
c64.SETNAM(strlen(filenameptr), filenameptr)
ubyte secondary = 1
uword end_of_load = 0
if address_override
secondary = 0
c64.SETLFS(1, drivenumber, secondary)
%asm {{
stx P8ZP_SCRATCH_REG
lda #0
ldx address_override
ldy address_override+1
jsr c64.LOAD
bcs +
stx end_of_load
sty end_of_load+1
+ ldx P8ZP_SCRATCH_REG
}}
if end_of_load
return end_of_load - address_override
return 0
}
str filename = "0:??????????????????????????????????????"
sub delete(ubyte drivenumber, uword filenameptr) {
; -- delete a file on the drive
ubyte flen = strlen(filenameptr)
filename[0] = 's'
filename[1] = ':'
memcopy(filenameptr, &filename+2, flen+1)
c64.SETNAM(flen+2, filename)
c64.SETLFS(1, drivenumber, 15)
void c64.OPEN()
c64.CLRCHN()
c64.CLOSE(1)
}
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
; -- rename a file on the drive
ubyte flen_old = strlen(oldfileptr)
ubyte flen_new = strlen(newfileptr)
filename[0] = 'r'
filename[1] = ':'
memcopy(newfileptr, &filename+2, flen_new)
ubyte fis_ix = flen_new+2 ; TODO is temp var for array indexing
filename[fis_ix] = '='
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
c64.SETNAM(3+flen_new+flen_old, filename)
c64.SETLFS(1, drivenumber, 15)
void c64.OPEN()
c64.CLRCHN()
c64.CLOSE(1)
}
}

View File

@ -1469,3 +1469,22 @@ shift_right_w_3 .proc
jmp shift_right_w_7._shift3
.pend
; support for bit shifting that is too large to be unrolled:
lsr_byte_A .proc
; -- lsr signed byte in A times the value in Y (assume >0)
cmp #0
bmi _negative
- lsr a
dey
bne -
rts
_negative lsr a
ora #$80
dey
bne _negative
rts
.pend

View File

@ -40,16 +40,6 @@ add_a_to_zpword .proc
+ rts
.pend
pop_index_times_5 .proc
inx
lda P8ESTACK_LO,x
asl a
asl a
clc
adc P8ESTACK_LO,x
rts
.pend
neg_b .proc
lda #0
sec
@ -1990,6 +1980,38 @@ strcpy .proc
.pend
strcmp_mem .proc
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
lda (P8ZP_SCRATCH_W1),y
bne +
lda (P8ZP_SCRATCH_W2),y
bne _return_minusone
beq _return
+ lda (P8ZP_SCRATCH_W2),y
sec
sbc (P8ZP_SCRATCH_W1),y
bmi _return_one
bne _return_minusone
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_return_one
lda #1
_return rts
_return_minusone
lda #-1
rts
.pend
func_leftstr .proc
; leftstr(source, target, length) with params on stack
inx
@ -2098,3 +2120,18 @@ _startloop dey
rts
.pend
func_strcmp .proc
inx
lda P8ESTACK_LO,x
sta P8ZP_SCRATCH_W2
lda P8ESTACK_HI,x
sta P8ZP_SCRATCH_W2+1
lda P8ESTACK_HI+1,x
tay
lda P8ESTACK_LO+1,x
jsr strcmp_mem
sta P8ESTACK_LO+1,x
rts
.pend

View File

@ -1 +1 @@
4.5
4.6

View File

@ -38,6 +38,7 @@ private fun compileMain(args: Array<String>) {
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
@ -62,7 +63,7 @@ private fun compileMain(args: Array<String>) {
println("Continuous watch mode active. Main module: $filepath")
try {
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) {
print(" ")
@ -87,7 +88,7 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {

View File

@ -121,7 +121,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(datatypeString(decl.datatype))
if(decl.arraysize!=null) {
decl.arraysize!!.index.accept(this)
decl.arraysize!!.indexNum?.accept(this)
decl.arraysize!!.indexVar?.accept(this)
}
if(decl.isArray)
output("]")
@ -352,9 +353,10 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
arrayIndexedExpression.identifier.accept(this)
arrayIndexedExpression.arrayvar.accept(this)
output("[")
arrayIndexedExpression.arrayspec.index.accept(this)
arrayIndexedExpression.indexer.indexNum?.accept(this)
arrayIndexedExpression.indexer.indexVar?.accept(this)
output("]")
}

View File

@ -6,7 +6,6 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.functions.BuiltinFunctions
import java.nio.file.Path
@ -37,6 +36,18 @@ interface Node {
throw FatalAstException("scope missing from $this")
}
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
fun containingStatement(): Statement {
if(this is Statement)
return this
return findParentNode<Statement>(this)!!
}
fun replaceChildNode(node: Node, replacement: Node)
}
@ -47,6 +58,7 @@ interface IFunctionCall {
class AsmGenInfo {
var usedAutoArrayIndexerForStatements = mutableMapOf<String, MutableSet<Statement>>()
var usedRegsaveA = false
var usedRegsaveX = false
var usedRegsaveY = false
@ -172,7 +184,6 @@ interface INameScope {
}
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoVars() = statements.all { it !is VarDecl }
fun containsNoCodeNorVars() = !containsCodeOrVars()
fun remove(stmt: Statement) {
@ -212,6 +223,14 @@ interface INameScope {
else
null
}
fun indexOfChild(stmt: Statement): Int {
val idx = statements.indexOfFirst { it===stmt }
if(idx>=0)
return idx
else
throw FatalAstException("attempt to find a non-child")
}
}
interface IAssignable {
@ -245,7 +264,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
override val position: Position = Position.DUMMY
override var parent: Node
get() = throw FatalAstException("program has no parent")
set(value) = throw FatalAstException("can't set parent of program")
set(_) = throw FatalAstException("can't set parent of program")
override fun linkParents(parent: Node) {
modules.forEach {
@ -301,7 +320,7 @@ class Module(override val name: String,
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>()
override val statements = mutableListOf<Statement>() // not used
override var parent: Node = ParentSentinel
override val asmGenInfo = AsmGenInfo()

View File

@ -470,7 +470,6 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
litval.stringliteral()!=null -> litval.stringliteral().toAst()
litval.charliteral()!=null -> {
try {
val cc=litval.charliteral()
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
@ -648,7 +647,6 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
}
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"

View File

@ -42,12 +42,6 @@ internal fun Module.checkImportedValid() {
imr.applyModifications()
}
internal fun Program.checkRecursion(errors: ErrorReporter) {
val checker = AstRecursionChecker(namespace, errors)
checker.visit(this)
checker.processMessages(name)
}
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val checker2 = AstIdentifiersChecker(this, errors)

View File

@ -41,8 +41,8 @@ sealed class Expression: Node {
&& other.left isSameAs left
&& other.right isSameAs right)
is ArrayIndexedExpression -> {
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index)
(other is ArrayIndexedExpression && other.arrayvar.nameInSource == arrayvar.nameInSource
&& other.indexer isSameAs indexer)
}
is DirectMemoryRead -> {
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
@ -232,20 +232,19 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
}
}
class ArrayIndexedExpression(var identifier: IdentifierReference,
val arrayspec: ArrayIndex,
class ArrayIndexedExpression(var arrayvar: IdentifierReference,
val indexer: ArrayIndex,
override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.linkParents(this)
arrayspec.linkParents(this)
arrayvar.linkParents(this)
indexer.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===identifier -> identifier = replacement as IdentifierReference
node===arrayspec.index -> arrayspec.index = replacement as Expression
node===arrayvar -> arrayvar = replacement as IdentifierReference
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
@ -255,10 +254,10 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = identifier.referencesIdentifier(*scopedName)
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val target = identifier.targetStatement(program.namespace)
val target = arrayvar.targetStatement(program.namespace)
if (target is VarDecl) {
return when (target.datatype) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
@ -270,7 +269,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
}
override fun toString(): String {
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
}
}

View File

@ -120,11 +120,6 @@ internal class AstChecker(private val program: Program,
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
errors.err("for loop requires a variable to loop with", forLoop.position)
} else {
fun checkLoopRangeValues() {
}
when (loopvar.datatype) {
DataType.UBYTE -> {
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
@ -464,6 +459,7 @@ internal class AstChecker(private val program: Program,
else {
if(variable.datatype !in ArrayDatatypes
&& variable.type!=VarDeclType.MEMORY
&& variable.struct == null
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
errors.err("invalid pointer-of operand type", addressOf.position)
}
@ -474,7 +470,7 @@ internal class AstChecker(private val program: Program,
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true)
err("recursive var declaration")
// CONST can only occur on simple types (byte, word, float)
@ -1012,7 +1008,7 @@ internal class AstChecker(private val program: Program,
}
}
} else if(postIncrDecr.target.arrayindexed != null) {
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program.namespace)
if(target==null) {
errors.err("undefined symbol", postIncrDecr.position)
}
@ -1027,32 +1023,38 @@ internal class AstChecker(private val program: Program,
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
val target = arrayIndexedExpression.identifier.targetStatement(program.namespace)
val target = arrayIndexedExpression.arrayvar.targetStatement(program.namespace)
if(target is VarDecl) {
if(target.datatype !in IterableDatatypes)
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
val arraysize = target.arraysize?.constIndex()
if(arraysize!=null) {
// check out of bounds
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
val index = arrayIndexedExpression.indexer.constIndex()
if(index!=null && (index<0 || index>=arraysize))
errors.err("array index out of bounds", arrayIndexedExpression.arrayspec.position)
errors.err("array index out of bounds", arrayIndexedExpression.indexer.position)
} else if(target.datatype == DataType.STR) {
if(target.value is StringLiteralValue) {
// check string lengths for non-memory mapped strings
val stringLen = (target.value as StringLiteralValue).value.length
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
val index = arrayIndexedExpression.indexer.constIndex()
if (index != null && (index < 0 || index >= stringLen))
errors.err("index out of bounds", arrayIndexedExpression.arrayspec.position)
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
}
}
} else
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
// check index value 0..255
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program).typeOrElse(DataType.STRUCT)
if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE)
val dtxNum = arrayIndexedExpression.indexer.indexNum?.inferType(program)?.typeOrElse(DataType.STRUCT)
if(dtxNum!=null && dtxNum != DataType.UBYTE && dtxNum != DataType.BYTE)
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
val dtxVar = arrayIndexedExpression.indexer.indexVar?.inferType(program)?.typeOrElse(DataType.STRUCT)
if(dtxVar!=null && dtxVar != DataType.UBYTE && dtxVar != DataType.BYTE)
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
if(arrayIndexedExpression.indexer.origExpression!=null)
throw FatalAstException("array indexer should have been replaced with a temp var @ ${arrayIndexedExpression.indexer.position}")
super.visit(arrayIndexedExpression)
}
@ -1157,10 +1159,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>256)
return err("byte array length must be 1-256")
val constX = arrayspec.index.constValue(program)
if(constX?.type !in IntegerDatatypes)
return err("array size specifier must be constant integer value")
val expectedSize = constX!!.number.toInt()
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return true
@ -1179,10 +1178,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>128)
return err("word array length must be 1-128")
val constX = arrayspec.index.constValue(program)
if(constX?.type !in IntegerDatatypes)
return err("array size specifier must be constant integer value")
val expectedSize = constX!!.number.toInt()
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return true
@ -1201,10 +1197,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize < 1 || arraySpecSize>51)
return err("float array length must be 1-51")
val constX = arrayspec.index.constValue(program)
if(constX?.type !in IntegerDatatypes)
return err("array size specifier must be constant integer value")
val expectedSize = constX!!.number.toInt()
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
} else

View File

@ -80,6 +80,11 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
if(decl.definingBlock().name==decl.name)
nameError(decl.name, decl.position, decl.definingBlock())
if(decl.definingSubroutine()?.name==decl.name)
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
super.visit(decl)
}

View File

@ -1,118 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
internal class AstRecursionChecker(private val namespace: INameScope,
private val errors: ErrorReporter) : IAstVisitor {
private val callGraph = DirectedGraph<INameScope>()
fun processMessages(modulename: String) {
val cycle = callGraph.checkForCycle()
if(cycle.isEmpty())
return
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val scope = functionCallStatement.definingScope()
val targetStatement = functionCallStatement.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall) {
val scope = functionCall.definingScope()
val targetStatement = functionCall.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
super.visit(functionCall)
}
private class DirectedGraph<VT> {
private val graph = mutableMapOf<VT, MutableSet<VT>>()
private var uniqueVertices = mutableSetOf<VT>()
val numVertices : Int
get() = uniqueVertices.size
fun add(from: VT, to: VT) {
var targets = graph[from]
if(targets==null) {
targets = mutableSetOf()
graph[from] = targets
}
targets.add(to)
uniqueVertices.add(from)
uniqueVertices.add(to)
}
fun print() {
println("#vertices: $numVertices")
graph.forEach { (from, to) ->
println("$from CALLS:")
to.forEach { println(" $it") }
}
val cycle = checkForCycle()
if(cycle.isNotEmpty()) {
println("CYCLIC! $cycle")
}
}
fun checkForCycle(): MutableList<VT> {
val visited = uniqueVertices.associateWith { false }.toMutableMap()
val recStack = uniqueVertices.associateWith { false }.toMutableMap()
val cycle = mutableListOf<VT>()
for(node in uniqueVertices) {
if(isCyclicUntil(node, visited, recStack, cycle))
return cycle
}
return mutableListOf()
}
private fun isCyclicUntil(node: VT,
visited: MutableMap<VT, Boolean>,
recStack: MutableMap<VT, Boolean>,
cycleNodes: MutableList<VT>): Boolean {
if(recStack[node]==true) return true
if(visited[node]==true) return false
// mark current node as visited and add to recursion stack
visited[node] = true
recStack[node] = true
// recurse for all neighbours
val neighbors = graph[node]
if(neighbors!=null) {
for (neighbour in neighbors) {
if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) {
cycleNodes.add(node)
return true
}
}
}
// pop node from recursion stack
recStack[node] = false
return false
}
}
}

View File

@ -9,78 +9,58 @@ import prog8.ast.statements.*
interface IAstModification {
fun perform()
class Remove(val node: Node, val parent: Node) : IAstModification {
class Remove(val node: Node, val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
} else {
throw FatalAstException("parent of a remove modification is not an INameScope")
}
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
}
}
class SetExpression(val setter: (newExpr: Expression) -> Unit, val newExpr: Expression, val parent: Node) : IAstModification {
class SetExpression(private val setter: (newExpr: Expression) -> Unit, private val newExpr: Expression, private val parent: Node) : IAstModification {
override fun perform() {
setter(newExpr)
newExpr.linkParents(parent)
}
}
class InsertFirst(val stmt: Statement, val parent: Node) : IAstModification {
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(0, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
parent.statements.add(0, stmt)
stmt.linkParents(parent as Node)
}
}
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
parent.statements.add(stmt)
stmt.linkParents(parent as Node)
}
}
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
val idx = parent.statements.indexOfFirst { it===after } + 1
parent.statements.add(idx, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
val idx = parent.statements.indexOfFirst { it===after } + 1
parent.statements.add(idx, stmt)
stmt.linkParents(parent as Node)
}
}
class InsertBefore(val before: Statement, val stmt: Statement, val parent: Node) : IAstModification {
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
val idx = parent.statements.indexOfFirst { it===before }
parent.statements.add(idx, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
val idx = parent.statements.indexOfFirst { it===before }
parent.statements.add(idx, stmt)
stmt.linkParents(parent as Node)
}
}
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) : IAstModification {
override fun perform() {
parent.replaceChildNode(node, replacement)
replacement.linkParents(parent)
}
}
class SwapOperands(val expr: BinaryExpression): IAstModification {
class SwapOperands(private val expr: BinaryExpression): IAstModification {
override fun perform() {
require(expr.operator in associativeOperators)
val tmp = expr.left
@ -363,8 +343,8 @@ abstract class AstWalker {
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
arrayIndexedExpression.identifier.accept(this, arrayIndexedExpression)
arrayIndexedExpression.arrayspec.accept(this, arrayIndexedExpression)
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
}

View File

@ -125,8 +125,8 @@ interface IAstVisitor {
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
arrayIndexedExpression.identifier.accept(this)
arrayIndexedExpression.arrayspec.accept(this)
arrayIndexedExpression.arrayvar.accept(this)
arrayIndexedExpression.indexer.accept(this)
}
fun visit(assignTarget: AssignTarget) {

View File

@ -1,5 +1,6 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.statements.Directive
@ -14,7 +15,7 @@ internal class ImportedModuleDirectiveRemover: AstWalker() {
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent))
return listOf(IAstModification.Remove(directive, parent as INameScope))
}
return noModifications
}

View File

@ -11,13 +11,13 @@ 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) {
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
// replace the literal string by a identifier reference to a new local vardecl
val vardecl = VarDecl.createAuto(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
IAstModification.InsertFirst(vardecl, string.definingScope())
)
}
return noModifications
@ -43,7 +43,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope() as Node)
IAstModification.InsertFirst(vardecl2, array.definingScope())
)
}
}

View File

@ -11,7 +11,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
// - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last.
// - blocks are ordered by address, where blocks without address are placed last.
// - in every scope, most directives and vardecls are moved to the top.
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
// - the 'start' subroutine is moved to the top.
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
@ -71,6 +71,69 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
is NumericLiteralValue -> {
arrayIndexedExpression.indexer.indexNum = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is IdentifierReference -> {
arrayIndexedExpression.indexer.indexVar = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is Expression -> {
// replace complex indexing with a temp variable
return getAutoIndexerVarFor(arrayIndexedExpression)
}
else -> return noModifications
}
}
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val subroutine = expr.definingSubroutine()!!
val statement = expr.containingStatement()
val indexerVarPrefix = "prog8_autovar_index_"
val indexerVarName: String
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
val freeVar = repo.filter { statement !in it.value }.map { it.key }.firstOrNull()
if(freeVar==null) {
// add another loop index var to be used for this expression
val statementId = expr.hashCode()
indexerVarName = "$indexerVarPrefix$statementId"
// create the indexer var at block level scope
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
var statements = repo[indexerVarName]
if(statements==null) {
statements = mutableSetOf()
repo[indexerVarName] = statements
}
statements.add(statement)
} else {
// reuse an already created indexer autovar
repo.getValue(freeVar).add(statement)
indexerVarName = freeVar
}
// assign the indexing expression to the helper variable, and replace the indexer with just the variable
val indexerExpression = expr.indexer.origExpression!!
val target = AssignTarget(IdentifierReference(listOf(indexerVarName), indexerExpression.position), null, null, indexerExpression.position)
val assign = Assignment(target, indexerExpression, indexerExpression.position)
val beforeWhat = expr.containingStatement()
modifications.add(IAstModification.InsertBefore(beforeWhat, assign, beforeWhat.definingScope()))
modifications.add(IAstModification.SetExpression( {
expr.indexer.indexVar = it as IdentifierReference
expr.indexer.indexNum = null
expr.indexer.origExpression = null
}, target.identifier!!.copy(), expr.indexer))
return modifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
@ -81,6 +144,28 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val declConstValue = declValue.constValue(program)
if(declConstValue==null) {
// move the vardecl (without value) to the scope and replace this with a regular assignment
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
if(decl.datatype!=DataType.FLOAT) {
decl.value = null
decl.allowInitializeWithZero = false
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, declValue, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope())
)
}
}
}
return noModifications
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment)
@ -104,8 +189,9 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
modifications.add(IAstModification.Remove(assignment, parent))
val scope = assignment.definingScope()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
modifications.add(IAstModification.Remove(assignment, scope))
return modifications
}
@ -162,54 +248,40 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
}
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)
}
return flattenArrayAssign(targetVar, alv, identifier, assign.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()
}
val alv = sourceVar.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
}
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
if(targetVar.arraysize==null) {
errors.err("array has no defined size", identifier.position)
return emptyList()
}
val alv = sourceVar.value as? ArrayLiteralValue
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
errors.err("element count mismatch", assign.position)
errors.err("element count mismatch", position)
return emptyList()
}
// TODO use a pointer loop instead of individual assignments
return alv.value.withIndex().map { (index, value)->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, assign.position), assign.position), assign.position)
Assignment(AssignTarget(null, idx, null, assign.position), value, value.position)
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
Assignment(AssignTarget(null, idx, null, position), value, value.position)
}
}

View File

@ -23,6 +23,11 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
val valueDt = declValue.inferType(program)
if(!valueDt.istype(decl.datatype)) {
// don't add a typecast on an array initializer value
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
return noModifications
return listOf(IAstModification.ReplaceNode(
declValue,
TypecastExpression(declValue, decl.datatype, true, declValue.position),

View File

@ -12,7 +12,7 @@ internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent))
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {

View File

@ -5,6 +5,7 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.compiler.CompilerException
import prog8.compiler.target.CompilationTarget
@ -29,12 +30,6 @@ sealed class Statement : Node {
scope.add(name)
return scope.joinToString(".")
}
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
}
@ -177,6 +172,7 @@ open class VarDecl(val type: VarDeclType,
private set
var structHasBeenFlattened = false // set later
private set
var allowInitializeWithZero = true
// prefix for literal values that are turned into a variable on the heap
@ -248,7 +244,12 @@ open class VarDecl(val type: VarDeclType,
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
}
fun zeroElementValue() = defaultZero(declaredDatatype, position)
fun zeroElementValue(): NumericLiteralValue {
if(allowInitializeWithZero)
return defaultZero(declaredDatatype, position)
else
throw CompilerException("attempt to get zero value for vardecl that shouldn't get it")
}
fun flattenStructMembers(): MutableList<Statement> {
val result = struct!!.statements.withIndex().map {
@ -277,36 +278,72 @@ class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Posit
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position)
class ArrayIndex(var index: Expression, override val position: Position) : Node {
class ArrayIndex(var origExpression: Expression?, // will be replaced later by either the number or the identifier
override val position: Position) : Node {
// for code simplicity, either indexed via a constant number or via a variable (no arbitrary expressions)
override lateinit var parent: Node
var indexNum: NumericLiteralValue? = origExpression as? NumericLiteralValue
var indexVar: IdentifierReference? = origExpression as? IdentifierReference
init {
if(indexNum!=null || indexVar!=null)
origExpression = null
}
override fun linkParents(parent: Node) {
this.parent = parent
index.linkParents(this)
origExpression?.linkParents(this)
indexNum?.linkParents(this)
indexVar?.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===index)
index = replacement
replacement.parent = this
require(replacement is Expression)
when {
node===origExpression -> origExpression = replacement
node===indexVar -> {
when (replacement) {
is NumericLiteralValue -> {
indexVar = null
indexNum = replacement
}
is IdentifierReference -> {
indexVar = replacement
}
else -> {
throw FatalAstException("invalid replace")
}
}
}
else -> throw FatalAstException("invalid replace")
}
}
companion object {
fun forArray(v: ArrayLiteralValue): ArrayIndex {
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
val indexnum = NumericLiteralValue.optimalNumeric(v.value.size, v.position)
return ArrayIndex(indexnum, v.position)
}
}
fun accept(visitor: IAstVisitor) = index.accept(visitor)
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
override fun toString(): String {
return("ArrayIndex($index, pos=$position)")
fun accept(visitor: IAstVisitor) {
origExpression?.accept(visitor)
indexNum?.accept(visitor)
indexVar?.accept(visitor)
}
fun accept(visitor: AstWalker, parent: Node) {
origExpression?.accept(visitor, this)
indexNum?.accept(visitor, this)
indexVar?.accept(visitor, this)
}
fun constIndex() = (index as? NumericLiteralValue)?.number?.toInt()
override fun toString(): String {
return("ArrayIndex($indexNum, $indexVar, pos=$position)")
}
infix fun isSameAs(other: ArrayIndex) = index.isSameAs(other.index)
fun constIndex() = indexNum?.number?.toInt()
infix fun isSameAs(other: ArrayIndex) = indexNum==other.indexNum && indexVar == other.indexVar
}
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
@ -407,17 +444,6 @@ data class AssignTarget(var identifier: IdentifierReference?,
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
companion object {
fun fromExpr(expr: Expression): AssignTarget {
return when (expr) {
is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
else -> throw FatalAstException("invalid expression object $expr")
}
}
}
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType { // TODO why does this have the extra 'stmt' scope parameter???
if (identifier != null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
@ -454,8 +480,8 @@ data class AssignTarget(var identifier: IdentifierReference?,
}
identifier != null -> value is IdentifierReference && value.nameInSource == identifier!!.nameInSource
arrayindexed != null -> {
if(value is ArrayIndexedExpression && value.identifier.nameInSource == arrayindexed!!.identifier.nameInSource)
arrayindexed!!.arrayspec isSameAs value.arrayspec
if(value is ArrayIndexedExpression && value.arrayvar.nameInSource == arrayindexed!!.arrayvar.nameInSource)
arrayindexed!!.indexer isSameAs value.indexer
else
false
}
@ -474,9 +500,9 @@ data class AssignTarget(var identifier: IdentifierReference?,
return addr1 != null && addr2 != null && addr1 == addr2
}
if (this.arrayindexed != null && other.arrayindexed != null) {
if (this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
if (this.arrayindexed!!.arrayvar.nameInSource == other.arrayindexed!!.arrayvar.nameInSource) {
val x1 = this.arrayindexed!!.indexer.constIndex()
val x2 = other.arrayindexed!!.indexer.constIndex()
return x1 != null && x2 != null && x1 == x2
}
}
@ -501,7 +527,7 @@ data class AssignTarget(var identifier: IdentifierReference?,
}
}
this.arrayindexed != null -> {
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
val targetStmt = this.arrayindexed!!.arrayvar.targetVarDecl(namespace)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
@ -898,11 +924,11 @@ class WhenStatement(var condition: Expression,
if(choice.values==null)
result.add(null to choice)
else {
val values = choice.values!!.map { it.constValue(program)?.number?.toInt() }
if(values.contains(null))
result.add(null to choice)
else
result.add(values.filterNotNull() to choice)
val values = choice.values!!.map {
val cv = it.constValue(program)
cv?.number?.toInt() ?: it.hashCode() // the hashcode is a nonsensical number but it avoids weird AST validation errors later
}
result.add(values to choice)
}
}
return result
@ -924,9 +950,15 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===statements)
statements = replacement
replacement.parent = this
val choiceValues = values
if(replacement is AnonymousScope && node===statements) {
statements = replacement
replacement.parent = this
} else if(choiceValues!=null && node in choiceValues) {
throw FatalAstException("cannot replace choice values")
} else {
throw FatalAstException("invalid replacement")
}
}
override fun toString(): String {

View File

@ -19,11 +19,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
if(nextAssign!=null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
decl.value = null
else
decl.value = decl.zeroElementValue()
if(decl.allowInitializeWithZero)
{
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
decl.value = null
else
decl.value = decl.zeroElementValue()
}
}
return noModifications
}
@ -46,14 +49,14 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// use the other part of the expression to split.
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, parent),
IAstModification.InsertBefore(assignment, assignRight, assignment.definingScope()),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
} else {
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, parent),
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
}
@ -124,7 +127,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
&& outerStatements[subroutineStmtIdx - 1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
return mods
}

View File

@ -28,7 +28,9 @@ data class CompilationOptions(val output: OutputType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean,
val noSysInit: Boolean)
val noSysInit: Boolean) {
var slowCodegenWarnings = false
}
class CompilerException(message: String?) : Exception(message)

View File

@ -29,6 +29,7 @@ class CompilationResult(val success: Boolean,
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
compilationTarget: String,
outputDir: Path): CompilationResult {
var programName = ""
@ -49,6 +50,7 @@ fun compileProgram(filepath: Path,
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
@ -208,7 +210,8 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
errors.handle()
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
val callGraph = CallGraph(programAst)
callGraph.checkRecursiveCalls(errors)
errors.handle()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()

View File

@ -126,8 +126,10 @@ internal object C64MachineDefinition: IMachineDefinition {
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
free.addAll(listOf(
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x16, 0x17, 0x18, 0x19, 0x1a,
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
@ -148,16 +150,16 @@ internal object C64MachineDefinition: IMachineDefinition {
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
))
}
if(options.zeropage!=ZeropageType.DONTUSE) {
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
} else {
// don't use the zeropage at all
free.clear()

View File

@ -66,16 +66,21 @@ internal class AsmGen(private val program: Program,
block2asm(b)
footer()
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
if(optimize) {
assemblyLines.clear()
assemblyLines.addAll(outputFile.readLines())
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
}
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
}
return AssemblyProgram(program.name, outputDir)
@ -545,8 +550,6 @@ internal class AsmGen(private val program: Program,
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
private val saveRegisterLabels = Stack<String>()
internal fun saveRegister(register: CpuRegister, dontUseStack: Boolean, scope: Subroutine?) {
if(dontUseStack) {
when (register) {
@ -673,132 +676,75 @@ internal class AsmGen(private val program: Program,
register: CpuRegister,
addOneExtra: Boolean=false) {
val reg = register.toString().toLowerCase()
val index = expr.arrayspec.index
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * elementDt.memorySize() + if(addOneExtra) 1 else 0
val indexnum = expr.indexer.constIndex()
if(indexnum!=null) {
val indexValue = indexnum * elementDt.memorySize() + if(addOneExtra) 1 else 0
out(" ld$reg #$indexValue")
return
}
val indexName = asmVariableName(expr.indexer.indexVar!!)
if(addOneExtra) {
// add 1 to the result
if (index is IdentifierReference) {
val indexName = asmVariableName(index)
when(elementDt) {
in ByteDatatypes -> {
out(" ldy $indexName | iny")
when(register) {
CpuRegister.A -> out(" tya")
CpuRegister.X -> out(" tyx")
CpuRegister.Y -> {}
}
when(elementDt) {
in ByteDatatypes -> {
out(" ldy $indexName | iny")
when(register) {
CpuRegister.A -> out(" tya")
CpuRegister.X -> out(" tyx")
CpuRegister.Y -> {}
}
in WordDatatypes -> {
out(" lda $indexName | sec | rol a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
sec
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
else -> throw AssemblyError("weird dt")
}
}
else {
expressionsAsmGen.translateExpression(index)
out("""
inc P8ESTACK_LO,x
bne +
inc P8ESTACK_HI,x
+""")
when(register) {
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
in WordDatatypes -> {
out(" lda $indexName | sec | rol a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
sec
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
else -> throw AssemblyError("weird dt")
}
} else {
if (index is IdentifierReference) {
val indexName = asmVariableName(index)
when(elementDt) {
in ByteDatatypes -> out(" ld$reg $indexName")
in WordDatatypes -> {
out(" lda $indexName | asl a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
when(elementDt) {
in ByteDatatypes -> out(" ld$reg $indexName")
in WordDatatypes -> {
out(" lda $indexName | asl a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
clc
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
else -> throw AssemblyError("weird dt")
}
}
else {
expressionsAsmGen.translateExpression(index)
when(elementDt) {
in ByteDatatypes -> {
when (register) {
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
clc
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
in WordDatatypes -> {
out("""
inx
lda P8ESTACK_LO,x
asl a""")
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
inx
lda P8ESTACK_LO,x
asl a
asl a
clc
adc P8ESTACK_LO,x""")
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
else -> throw AssemblyError("weird dt")
}
else -> throw AssemblyError("weird dt")
}
}
}
@ -806,6 +752,9 @@ internal class AsmGen(private val program: Program,
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateExpression(indexer: ArrayIndex) =
expressionsAsmGen.translateExpression(indexer)
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
@ -983,14 +932,12 @@ internal class AsmGen(private val program: Program,
}
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
if(constIterations==0)
return
// note: A/Y must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter")
out("""
bne +
cpy #0
bne +
beq $endLabel
+ sta $counterVar
sta $counterVar
sty $counterVar+1
$repeatLabel lda $counterVar
bne +
@ -1016,10 +963,13 @@ $counterVar .word 0""")
}
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
if(constIterations==0)
return
// note: A must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter")
if(constIterations==null)
out(" beq $endLabel")
out("""
beq $endLabel
sta $counterVar
$repeatLabel""")
translate(body)

View File

@ -196,8 +196,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx "))
) {
val firstLoc = first.substring(4)
val secondLoc = second.substring(4)
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
}

View File

@ -159,8 +159,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror2_array_ub")
}
is DirectMemoryRead -> {
@ -182,8 +182,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror2_array_uw")
}
is IdentifierReference -> {
@ -204,8 +204,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror_array_ub")
}
is DirectMemoryRead -> {
@ -234,8 +234,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror_array_uw")
}
is IdentifierReference -> {
@ -256,8 +256,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol2_array_ub")
}
is DirectMemoryRead -> {
@ -279,8 +279,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol2_array_uw")
}
is IdentifierReference -> {
@ -301,8 +301,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol_array_ub")
}
is DirectMemoryRead -> {
@ -331,8 +331,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol_array_uw")
}
is IdentifierReference -> {
@ -478,19 +478,26 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
if(first is ArrayIndexedExpression && second is ArrayIndexedExpression) {
val indexValue1 = first.arrayspec.index as? NumericLiteralValue
val indexName1 = first.arrayspec.index as? IdentifierReference
val indexValue2 = second.arrayspec.index as? NumericLiteralValue
val indexName2 = second.arrayspec.index as? IdentifierReference
val arrayVarName1 = asmgen.asmVariableName(first.identifier)
val arrayVarName2 = asmgen.asmVariableName(second.identifier)
val arrayVarName1 = asmgen.asmVariableName(first.arrayvar)
val arrayVarName2 = asmgen.asmVariableName(second.arrayvar)
val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT)
if(indexValue1!=null && indexValue2!=null) {
swapArrayValues(elementDt, arrayVarName1, indexValue1, arrayVarName2, indexValue2)
val firstNum = first.indexer.indexNum
val firstVar = first.indexer.indexVar
val secondNum = second.indexer.indexNum
val secondVar = second.indexer.indexVar
if(firstNum!=null && secondNum!=null) {
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondNum)
return
} else if(indexName1!=null && indexName2!=null) {
swapArrayValues(elementDt, arrayVarName1, indexName1, arrayVarName2, indexName2)
} else if(firstVar!=null && secondVar!=null) {
swapArrayValues(elementDt, arrayVarName1, firstVar, arrayVarName2, secondVar)
return
} else if(firstNum!=null && secondVar!=null) {
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondVar)
return
} else if(firstVar!=null && secondNum!=null) {
swapArrayValues(elementDt, arrayVarName1, firstVar, arrayVarName2, secondNum)
return
}
}
@ -601,7 +608,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
lda $arrayVarName2+1,y
sta $arrayVarName1+1,x
pla
sta $arrayVarName2+1,y
sta $arrayVarName2+1,y
ldx P8ZP_SCRATCH_REG
""")
}
@ -636,6 +643,122 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) {
val index1 = indexValue1.number.toInt() * elementDt.memorySize()
val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
lda $arrayVarName1 + $index1
pha
ldy $idxAsmName2
lda $arrayVarName2,y
sta $arrayVarName1 + $index1
pla
sta $arrayVarName2,y
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
lda $arrayVarName1 + $index1
pha
lda $idxAsmName2
asl a
tay
lda $arrayVarName2,y
sta $arrayVarName1 + $index1
pla
sta $arrayVarName2,y
lda $arrayVarName1 + $index1+1
pha
lda $arrayVarName2+1,y
sta $arrayVarName1 + $index1+1
pla
sta $arrayVarName2+1,y
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1
lda #>(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1+1
lda #>$arrayVarName1
sta P8ZP_SCRATCH_W1+1
lda $idxAsmName2
asl a
asl a
clc
adc $idxAsmName2
adc #<$arrayVarName1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val idxAsmName1 = asmgen.asmVariableName(indexName1)
val index2 = indexValue2.number.toInt() * elementDt.memorySize()
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
lda $arrayVarName2 + $index2
pha
ldy $idxAsmName1
lda $arrayVarName1,y
sta $arrayVarName2 + $index2
pla
sta $arrayVarName1,y
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
lda $arrayVarName2 + $index2
pha
lda $idxAsmName1
asl a
tay
lda $arrayVarName1,y
sta $arrayVarName2 + $index2
pla
sta $arrayVarName1,y
lda $arrayVarName2 + $index2+1
pha
lda $arrayVarName1+1,y
sta $arrayVarName2 + $index2+1
pla
sta $arrayVarName1+1,y
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #>$arrayVarName1
sta P8ZP_SCRATCH_W1+1
lda $idxAsmName1
asl a
asl a
clc
adc $idxAsmName1
adc #<$arrayVarName1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ lda #<(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2
lda #>(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2+1
jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
}
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
translateFunctionArguments(fcall.args, func)
val dt = fcall.args.single().inferType(program)

View File

@ -3,6 +3,7 @@ package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
@ -90,6 +91,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
in ByteDatatypes -> translateByteEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
in WordDatatypes -> translateWordEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> translateFloatEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
DataType.STR -> translateStringEquals(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@ -131,6 +133,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
in ByteDatatypes -> translateByteNotEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
in WordDatatypes -> translateWordNotEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
DataType.FLOAT -> translateFloatNotEquals(left, right, leftConstVal, rightConstVal, jumpIfFalseLabel)
DataType.STR -> translateStringNotEquals(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@ -145,6 +148,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
translateExpression(right)
asmgen.out(" jsr floats.less_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
}
DataType.STR -> translateStringLess(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@ -159,6 +163,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
translateExpression(right)
asmgen.out(" jsr floats.lesseq_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
}
DataType.STR -> translateStringLessOrEqual(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@ -173,6 +178,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
translateExpression(right)
asmgen.out(" jsr floats.greater_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
}
DataType.STR -> translateStringGreater(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@ -187,6 +193,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
translateExpression(right)
asmgen.out(" jsr floats.greatereq_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
}
DataType.STR -> translateStringGreaterOrEqual(left as IdentifierReference, right as IdentifierReference, jumpIfFalseLabel)
else -> throw AssemblyError("weird operand datatype")
}
}
@ -942,6 +949,97 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
asmgen.out(" jsr floats.notequal_f | inx | lda P8ESTACK_LO,x | beq $jumpIfFalseLabel")
}
private fun translateStringEquals(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
cmp #0
bne $jumpIfFalseLabel""")
}
private fun translateStringNotEquals(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
cmp #0
beq $jumpIfFalseLabel""")
}
private fun translateStringLess(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
bpl $jumpIfFalseLabel""")
}
private fun translateStringGreater(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
beq $jumpIfFalseLabel
bmi $jumpIfFalseLabel""")
}
private fun translateStringLessOrEqual(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
beq +
bpl $jumpIfFalseLabel
+""")
}
private fun translateStringGreaterOrEqual(left: IdentifierReference, right: IdentifierReference, jumpIfFalseLabel: String) {
val leftNam = asmgen.asmVariableName(left)
val rightNam = asmgen.asmVariableName(right)
asmgen.out("""
lda #<$rightNam
sta P8ZP_SCRATCH_W2
lda #>$rightNam
sta P8ZP_SCRATCH_W2+1
lda #<$leftNam
ldy #>$leftNam
jsr prog8_lib.strcmp_mem
beq +
bmi $jumpIfFalseLabel
+""")
}
private fun translateFunctionCallResultOntoStack(expression: FunctionCall) {
val functionName = expression.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
@ -1125,6 +1223,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
private fun translateExpression(expr: BinaryExpression) {
// TODO needs to use optimized assembly generation like the assignment instructions. But avoid code duplication.... rewrite all expressions into assignment form?
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
@ -1134,6 +1233,106 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
// see if we can apply some optimized routines
when(expr.operator) {
"+" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val leftVal = expr.left.constValue(program)?.number?.toInt()
val rightVal = expr.right.constValue(program)?.number?.toInt()
if (leftVal!=null && leftVal in -4..4) {
translateExpression(expr.right)
if(rightDt in ByteDatatypes) {
val incdec = if(leftVal<0) "dec" else "inc"
repeat(leftVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(leftVal<0) {
repeat(leftVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(leftVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
else if (rightVal!=null && rightVal in -4..4)
{
translateExpression(expr.left)
if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "dec" else "inc"
repeat(rightVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(rightVal<0) {
repeat(rightVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(rightVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
}
}
"-" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if (rightVal!=null && rightVal in -4..4)
{
translateExpression(expr.left)
if(leftDt in ByteDatatypes) {
val incdec = if(rightVal<0) "inc" else "dec"
repeat(rightVal.absoluteValue) {
asmgen.out(" $incdec P8ESTACK_LO+1,x")
}
} else {
// word
if(rightVal>0) {
repeat(rightVal.absoluteValue) {
asmgen.out("""
lda P8ESTACK_LO+1,x
bne +
dec P8ESTACK_HI+1,x
+ dec P8ESTACK_LO+1,x""")
}
} else {
repeat(rightVal) {
asmgen.out("""
inc P8ESTACK_LO+1,x
bne +
inc P8ESTACK_HI+1,x
+""")
}
}
}
return
}
}
}
">>" -> {
translateExpression(expr.left)
val amount = expr.right.constValue(program)?.number?.toInt()
@ -1215,6 +1414,16 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(value!=null) {
if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt()
if(amount==2) {
// optimize x*2 common case
translateExpression(expr.left)
if(leftDt in ByteDatatypes) {
asmgen.out(" asl P8ESTACK_LO+1,x")
} else {
asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x")
}
return
}
when(rightDt) {
DataType.UBYTE -> {
if(amount in asmgen.optimizedByteMultiplications) {
@ -1259,20 +1468,41 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
}
"/" -> {
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if(rightVal!=null && rightVal==2) {
translateExpression(expr.left)
when(leftDt) {
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
DataType.BYTE -> asmgen.out(" asl P8ESTACK_LO+1,x | ror P8ESTACK_LO+1,x")
DataType.UWORD -> asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
DataType.WORD -> asmgen.out(" asl P8ESTACK_HI+1,x | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
else -> throw AssemblyError("wrong dt")
}
return
}
}
}
}
// the general, non-optimized cases
translateExpression(expr.left)
translateExpression(expr.right)
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
else -> throw AssemblyError("non-numerical datatype")
// the general, non-optimized cases TODO optimize more cases....
translateExpression(expr.left)
translateExpression(expr.right)
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in comparisonOperators) {
translateCompareStrings(expr.operator)
}
else {
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
else -> throw AssemblyError("non-numerical datatype")
}
}
}
@ -1313,11 +1543,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
val index = arrayExpr.arrayspec.index
val elementDt = arrayExpr.inferType(program).typeOrElse(DataType.STRUCT)
val arrayVarName = asmgen.asmVariableName(arrayExpr.identifier)
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * elementDt.memorySize()
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
if(arrayExpr.indexer.indexNum!=null) {
val indexValue = arrayExpr.indexer.constIndex()!! * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
@ -1356,6 +1585,14 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
fun translateExpression(indexer: ArrayIndex) {
// it is either a number, or a variable
val indexNum = indexer.indexNum
val indexVar = indexer.indexVar
indexNum?.let { asmgen.translateExpression(indexNum) }
indexVar?.let { asmgen.translateExpression(indexVar) }
}
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
@ -1444,4 +1681,36 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateCompareStrings(operator: String) {
asmgen.out(" jsr prog8_lib.func_strcmp | lda P8ESTACK_LO+1,x") // result of compare in A
when(operator) {
"==" -> asmgen.out(" and #1 | eor #1 | sta P8ESTACK_LO+1,x")
"!=" -> asmgen.out(" and #1 | sta P8ESTACK_LO+1,x")
"<=" -> asmgen.out("""
bpl +
lda #1
bne ++
+ lda #0
+ sta P8ESTACK_LO+1,x""")
">=" -> asmgen.out("""
bmi +
lda #1
bne ++
+ lda #0
+ sta P8ESTACK_LO+1,x""")
"<" -> asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO+1,x""")
">" -> asmgen.out("""
bpl +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO+1,x""")
}
}
}

View File

@ -70,67 +70,64 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.identifier)
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
when(index) {
is NumericLiteralValue -> {
val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
else
asmgen.out("""
lda $asmArrayvarname+$indexValue
bne +
dec $asmArrayvarname+$indexValue+1
if(targetArrayIdx.indexer.indexNum!=null) {
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
else
asmgen.out("""
lda $asmArrayvarname+$indexValue
bne +
dec $asmArrayvarname+$indexValue+1
+ dec $asmArrayvarname+$indexValue
""")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
else -> {
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X, false, scope)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
}
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
else
asmgen.out("""
lda $asmArrayvarname,x
bne +
dec $asmArrayvarname+1,x
+ dec $asmArrayvarname
""")
}
DataType.FLOAT -> {
asmgen.out("""
ldy #>$asmArrayvarname
clc
adc #<$asmArrayvarname
bcc +
iny
+ jsr floats.inc_var_f""")
}
else -> throw AssemblyError("weird array elt dt")
DataType.FLOAT -> {
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
asmgen.restoreRegister(CpuRegister.X, false)
else -> throw AssemblyError("need numeric type")
}
}
else
{
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X, false, scope)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
}
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
else
asmgen.out("""
lda $asmArrayvarname,x
bne +
dec $asmArrayvarname+1,x
+ dec $asmArrayvarname
""")
}
DataType.FLOAT -> {
asmgen.out("""
ldy #>$asmArrayvarname
clc
adc #<$asmArrayvarname
bcc +
iny
+ jsr floats.inc_var_f""")
}
else -> throw AssemblyError("weird array elt dt")
}
asmgen.restoreRegister(CpuRegister.X, false)
}
}
else -> throw AssemblyError("weird target type ${stmt.target}")
}
}
}

View File

@ -1,6 +1,5 @@
package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
@ -43,12 +42,12 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.identifier)
asmgen.asmVariableName(array.arrayvar)
lateinit var origAssign: AsmAssignment
@ -93,13 +92,13 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.identifier)
asmgen.asmVariableName(array.arrayvar)
companion object {
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
@ -154,11 +153,12 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
}
}
fun withAdjustedDt(newType: DataType) =
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
fun adjustSignedUnsigned(target: AsmAssignTarget): AsmAssignSource {
// allow some signed/unsigned relaxations
fun withAdjustedDt(newType: DataType) =
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
if(target.datatype!=datatype) {
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
return withAdjustedDt(target.datatype)

View File

@ -43,27 +43,22 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
SourceStorageKind.VARIABLE -> {
// simple case: assign from another variable
val variable = assign.source.asmVarname!!
val variable = assign.source.asmVarname
when (assign.target.datatype) {
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
DataType.UWORD, DataType.WORD -> assignVariableWord(assign.target, variable)
DataType.FLOAT -> assignVariableFloat(assign.target, variable)
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}")
}
}
SourceStorageKind.ARRAY -> {
val value = assign.source.array!!
val elementDt = assign.source.datatype
val index = value.arrayspec.index
val arrayVarName = asmgen.asmVariableName(value.identifier)
if (index is NumericLiteralValue) {
val arrayVarName = asmgen.asmVariableName(value.arrayvar)
if (value.indexer.indexNum!=null) {
// constant array index value
val indexValue = index.number.toInt() * elementDt.memorySize()
val indexValue = value.indexer.constIndex()!! * elementDt.memorySize()
when (elementDt) {
in ByteDatatypes ->
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
@ -118,8 +113,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
SourceStorageKind.EXPRESSION -> {
val value = assign.source.expression!!
when(value) {
when(val value = assign.source.expression!!) {
is AddressOf -> {
val sourceName = value.identifier.firstStructVarName(program.namespace) ?: asmgen.asmVariableName(value.identifier)
assignAddressOf(assign.target, sourceName)
@ -213,7 +207,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is FunctionCall -> {}
else -> {
// TODO optimize the others further?
println("warning: slow stack evaluation used for typecast: into $targetDt at ${value.position}")
if(this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for typecast: into $targetDt at ${value.position}")
}
}
@ -265,66 +260,60 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
storeByteViaRegisterAInMemoryAddress("P8ESTACK_LO,x", target.memory!!)
}
TargetStorageKind.ARRAY -> {
val index = target.array!!.arrayspec.index
when {
target.constArrayIndexValue!=null -> {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
}
in WordDatatypes -> {
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta ${target.asmVarname}+$scaledIdx
lda P8ESTACK_HI,x
sta ${target.asmVarname}+$scaledIdx+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
jsr floats.pop_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
}
}
index is IdentifierReference -> {
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname},y")
}
DataType.UWORD, DataType.WORD -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta ${target.asmVarname},y
lda P8ESTACK_HI,x
sta ${target.asmVarname}+1,y
""")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
+ jsr floats.pop_float""")
}
else -> throw AssemblyError("weird dt")
in WordDatatypes -> {
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta ${target.asmVarname}+$scaledIdx
lda P8ESTACK_HI,x
sta ${target.asmVarname}+$scaledIdx+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
jsr floats.pop_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
}
else -> {
asmgen.translateExpression(index)
asmgen.out(" inx | lda P8ESTACK_LO,x")
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
}
else
{
target.array!!
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname},y")
}
DataType.UWORD, DataType.WORD -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta ${target.asmVarname},y
lda P8ESTACK_HI,x
sta ${target.asmVarname}+1,y
""")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
+ jsr floats.pop_float""")
}
else -> throw AssemblyError("weird dt")
}
}
}
@ -441,73 +430,66 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
throw AssemblyError("no asm gen for assign wordvar $sourceName to memory ${target.memory}")
}
TargetStorageKind.ARRAY -> {
val index = target.array!!.arrayspec.index
when {
target.constArrayIndexValue!=null -> {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
in WordDatatypes -> {
asmgen.out("""
lda $sourceName
sta ${target.asmVarname}+$scaledIdx
lda $sourceName+1
sta ${target.asmVarname}+$scaledIdx+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
jsr floats.copy_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
target.array!!
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
in WordDatatypes -> {
asmgen.out("""
lda $sourceName
sta ${target.asmVarname}+$scaledIdx
lda $sourceName+1
sta ${target.asmVarname}+$scaledIdx+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
jsr floats.copy_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
}
index is IdentifierReference -> {
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
}
DataType.UWORD, DataType.WORD -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out("""
lda $sourceName
sta ${target.asmVarname},y
lda $sourceName+1
sta ${target.asmVarname}+1,y
""")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
asmgen.out("""
ldy #<$sourceName
sty P8ZP_SCRATCH_W1
ldy #>$sourceName
sty P8ZP_SCRATCH_W1+1
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
}
else
{
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
}
DataType.UWORD, DataType.WORD -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out("""
lda $sourceName
sta ${target.asmVarname},y
lda $sourceName+1
sta ${target.asmVarname}+1,y
""")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
asmgen.out("""
ldy #<$sourceName
sty P8ZP_SCRATCH_W1
ldy #>$sourceName
sty P8ZP_SCRATCH_W1+1
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
+ jsr floats.copy_float""")
}
else -> throw AssemblyError("weird dt")
}
}
else -> {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | lda $sourceName+1 | sta P8ESTACK_HI,x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda P8ESTACK_LO,x")
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
else -> throw AssemblyError("weird dt")
}
}
}
@ -547,16 +529,23 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
// TODO optimize this, but the situation doesn't occur very often
// if(target.constArrayIndexValue!=null) {
// TODO("const index ${target.constArrayIndexValue}")
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
// TODO("array[var] ${target.constArrayIndexValue}")
// }
val index = target.array!!.arrayspec.index
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr floats.push_float")
asmgen.translateExpression(index)
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname} | jsr floats.pop_float_to_indexed_var")
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1""")
if(target.array!!.indexer.indexNum!=null) {
val index = target.array.indexer.constIndex()!!
asmgen.out(" lda #$index")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float")
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
@ -576,22 +565,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!)
}
TargetStorageKind.ARRAY -> {
val index = target.array!!.arrayspec.index
when {
target.constArrayIndexValue!=null -> {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
index is IdentifierReference -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
}
else -> {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda P8ESTACK_LO,x")
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
}
if (target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
else {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, target.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
}
}
TargetStorageKind.REGISTER -> {
@ -628,7 +608,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
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}")
if(this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for sign-extend byte typecast at ${bytevar.position}")
asmgen.translateExpression(wordtarget.origAssign.source.expression!!)
assignStackValue(wordtarget)
}
@ -686,22 +667,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
val index = wordtarget.array!!.arrayspec.index
when {
wordtarget.constArrayIndexValue!=null -> {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx | lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1")
}
index is IdentifierReference -> {
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array, wordtarget.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname},y | lda #0 | iny | sta ${wordtarget.asmVarname},y")
}
else -> {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | lda #0 | sta P8ESTACK_HI,x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda P8ESTACK_LO,x")
popAndWriteArrayvalueWithUnscaledIndexA(wordtarget.datatype, wordtarget.asmVarname)
}
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx | lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1")
}
else {
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array!!, wordtarget.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname},y | lda #0 | iny | sta ${wordtarget.asmVarname},y")
}
}
TargetStorageKind.REGISTER -> {
@ -734,40 +706,21 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
storeRegisterInMemoryAddress(register, target.memory!!)
}
TargetStorageKind.ARRAY -> {
val index = target.array!!.arrayspec.index
when (index) {
is NumericLiteralValue -> {
val memindex = index.number.toInt()
when {
target.constArrayIndexValue!=null -> {
when (register) {
CpuRegister.A -> asmgen.out(" sta ${target.asmVarname}+$memindex")
CpuRegister.X -> asmgen.out(" stx ${target.asmVarname}+$memindex")
CpuRegister.Y -> asmgen.out(" sty ${target.asmVarname}+$memindex")
CpuRegister.A -> asmgen.out(" sta ${target.asmVarname}+${target.constArrayIndexValue}")
CpuRegister.X -> asmgen.out(" stx ${target.asmVarname}+${target.constArrayIndexValue}")
CpuRegister.Y -> asmgen.out(" sty ${target.asmVarname}+${target.constArrayIndexValue}")
}
}
is IdentifierReference -> {
else -> {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
asmgen.out(" ldy ${asmgen.asmVariableName(index)} | sta ${target.asmVarname},y")
}
else -> {
asmgen.saveRegister(register, false, target.scope)
asmgen.translateExpression(index)
asmgen.restoreRegister(register, false)
when (register) {
CpuRegister.A -> asmgen.out(" sta P8ZP_SCRATCH_B1")
CpuRegister.X -> asmgen.out(" stx P8ZP_SCRATCH_B1")
CpuRegister.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1")
}
asmgen.out("""
inx
lda P8ESTACK_LO,x
tay
lda P8ZP_SCRATCH_B1
sta ${target.asmVarname},y
""")
asmgen.out(" ldy ${asmgen.asmVariableName(target.array!!.indexer.indexVar!!)} | sta ${target.asmVarname},y")
}
}
}
@ -880,19 +833,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}")
}
TargetStorageKind.ARRAY -> {
// TODO optimize this, but the situation doesn't occur very often
// if(target.constArrayIndexValue!=null) {
// TODO("const index ${target.constArrayIndexValue}")
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
// TODO("array[var] ${target.constArrayIndexValue}")
// }
val index = target.array!!.arrayspec.index
asmgen.translateExpression(index)
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y)
asmgen.out("""
inx
lda P8ESTACK_LO,x
asl a
tay
lda #<${word.toHex()}
sta ${target.asmVarname},y
lda #>${word.toHex()}
@ -927,25 +869,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
storeByteViaRegisterAInMemoryAddress("#${byte.toHex()}", target.memory!!)
}
TargetStorageKind.ARRAY -> {
val index = target.array!!.arrayspec.index
when {
target.constArrayIndexValue!=null -> {
val indexValue = target.constArrayIndexValue!!
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname}+$indexValue")
}
index is IdentifierReference -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
asmgen.out(" lda #<${byte.toHex()} | sta ${target.asmVarname},y")
}
else -> {
asmgen.translateExpression(index)
asmgen.out("""
inx
ldy P8ESTACK_LO,x
lda #${byte.toHex()}
sta ${target.asmVarname},y
""")
}
if (target.constArrayIndexValue!=null) {
val indexValue = target.constArrayIndexValue!!
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname}+$indexValue")
}
else {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UBYTE, CpuRegister.Y)
asmgen.out(" lda #<${byte.toHex()} | sta ${target.asmVarname},y")
}
}
TargetStorageKind.REGISTER -> when(target.register!!) {
@ -989,15 +919,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
// TODO optimize this, but the situation doesn't occur very often
// if(target.constArrayIndexValue!=null) {
// TODO("const index ${target.constArrayIndexValue}")
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
// TODO("array[var] ${target.constArrayIndexValue}")
// }
val index = target.array!!.arrayspec.index
if (index is NumericLiteralValue) {
val indexValue = index.number.toInt() * DataType.FLOAT.memorySize()
if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * DataType.FLOAT.memorySize()
asmgen.out("""
lda #0
sta ${target.asmVarname}+$indexValue
@ -1007,12 +930,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta ${target.asmVarname}+$indexValue+4
""")
} else {
asmgen.translateExpression(index)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
asmgen.out("""
lda #<${target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${target.asmVarname}
sta P8ZP_SCRATCH_W1+1
lda $asmvarname
jsr floats.set_0_array_float
""")
}
@ -1043,16 +967,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
// TODO optimize this, but the situation doesn't occur very often
// if(target.constArrayIndexValue!=null) {
// TODO("const index ${target.constArrayIndexValue}")
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
// TODO("array[var] ${target.constArrayIndexValue}")
// }
val index = target.array!!.arrayspec.index
val arrayVarName = target.asmVarname
if (index is NumericLiteralValue) {
val indexValue = index.number.toInt() * DataType.FLOAT.memorySize()
if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * DataType.FLOAT.memorySize()
asmgen.out("""
lda $constFloat
sta $arrayVarName+$indexValue
@ -1066,7 +983,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta $arrayVarName+$indexValue+4
""")
} else {
asmgen.translateExpression(index)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
asmgen.out("""
lda #<${constFloat}
sta P8ZP_SCRATCH_W1
@ -1076,6 +993,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta P8ZP_SCRATCH_W2
lda #>${arrayVarName}
sta P8ZP_SCRATCH_W2+1
lda $asmvarname
jsr floats.set_array_float
""")
}
@ -1220,6 +1138,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.storeByteIntoPointer(addressExpr, ldaInstructionArg)
}
else -> {
asmgen.out(" lda $ldaInstructionArg | pha")
asmgen.translateExpression(addressExpr)
asmgen.out("""
inx
@ -1227,8 +1146,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta P8ZP_SCRATCH_W2
lda P8ESTACK_HI,x
sta P8ZP_SCRATCH_W2+1
lda $ldaInstructionArg
ldy #0
pla
sta (P8ZP_SCRATCH_W2),y""")
}
}
@ -1266,24 +1185,4 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
}
private fun popAndWriteArrayvalueWithUnscaledIndexA(elementDt: DataType, asmArrayvarname: String) {
when (elementDt) {
in ByteDatatypes ->
asmgen.out(" tay | inx | lda P8ESTACK_LO,x | sta $asmArrayvarname,y")
in WordDatatypes ->
asmgen.out(" asl a | tay | inx | lda P8ESTACK_LO,x | sta $asmArrayvarname,y | lda P8ESTACK_HI,x | sta $asmArrayvarname+1,y")
DataType.FLOAT ->
// scaling * 5 is done in the subroutine that's called
asmgen.out("""
sta P8ESTACK_LO,x
dex
lda #<$asmArrayvarname
ldy #>$asmArrayvarname
jsr floats.pop_float_to_indexed_var
""")
else ->
throw AssemblyError("weird array type")
}
}
}

View File

@ -1,6 +1,5 @@
package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
@ -11,7 +10,6 @@ import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
import prog8.compiler.toHex
import kotlin.math.absoluteValue
internal class AugmentableAssignmentAsmGen(private val program: Program,
private val assignmentAsmGen: AssignmentAsmGen,
@ -21,8 +19,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
require(assign.isAugmentable)
require(assign.source.kind== SourceStorageKind.EXPRESSION)
val value = assign.source.expression!!
when (value) {
when (val value = assign.source.expression!!) {
is PrefixExpression -> {
// A = -A , A = +A, A = ~A, A = not A
val type = value.inferType(program).typeOrElse(DataType.STRUCT)
@ -182,7 +179,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
else -> {
println("warning: slow stack evaluation used (1): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO optimize...
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (1): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO optimize...
asmgen.translateExpression(memory.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
val zp = CompilationTarget.instance.machine.zeropage
@ -201,7 +199,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
TargetStorageKind.ARRAY -> {
println("*** TODO optimize simple inplace array assignment ${target.array} $operator= $value")
if(asmgen.options.slowCodegenWarnings)
println("*** TODO optimize simple inplace array assignment ${target.array} $operator= $value")
assignmentAsmGen.translateNormalAssignment(target.origAssign) // TODO get rid of this fallback for the most common cases here
}
TargetStorageKind.REGISTER -> TODO("reg in-place modification")
@ -222,7 +221,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
private fun inplaceModification_byte_value_to_memory(pointervar: IdentifierReference, operator: String, value: Expression) {
println("warning: slow stack evaluation used (3): @(${pointervar.nameInSource.last()}) $operator= ${value::class.simpleName} at ${value.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (3): @(${pointervar.nameInSource.last()}) $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value)
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
when (operator) {
@ -413,7 +413,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_byte_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) {
// this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow).
println("warning: slow stack evaluation used (5): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (5): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value)
when (operator) {
// note: ** (power) operator requires floats.
@ -497,25 +498,31 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"<<" -> {
asmgen.out("""
ldy $otherName
beq +
- asl $name
dey
bne -""")
bne -
+""")
}
">>" -> {
if(dt==DataType.UBYTE) {
asmgen.out("""
ldy $otherName
beq +
- lsr $name
dey
bne -""")
bne -
+""")
} else {
asmgen.out("""
ldy $otherName
beq +
- lda $name
asl a
ror $name
dey
bne -""")
bne -
+""")
}
}
"&" -> asmgen.out(" lda $name | and $otherName | sta $name")
@ -570,14 +577,30 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta $name""")
}
"<<" -> {
repeat(value) { asmgen.out(" asl $name") }
if(value>=8) asmgen.out(" lda #0 | sta $name")
else repeat(value) { asmgen.out(" asl $name") }
}
">>" -> {
if(value>0) {
if (dt == DataType.UBYTE) {
repeat(value) { asmgen.out(" lsr $name") }
if(value>=8) asmgen.out(" lda #0 | sta $name")
else repeat(value) { asmgen.out(" lsr $name") }
} else {
repeat(value) { asmgen.out(" lda $name | asl a | ror $name") }
when {
value>=8 -> asmgen.out("""
lda $name
bmi +
lda #0
beq ++
+ lda #-1
+ sta $name""")
value>3 -> asmgen.out("""
lda $name
ldy #$value
jsr math.lsr_byte_A
sta $name""")
else -> repeat(value) { asmgen.out(" lda $name | asl a | ror $name") }
}
}
}
}
@ -608,7 +631,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// TODO: tuned code for more operators
}
else -> {
inplaceModification_byte_value_to_variable(name, dt, operator, memread);
inplaceModification_byte_value_to_variable(name, dt, operator, memread)
}
}
}
@ -639,7 +662,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// TODO: tuned code for more operators
}
else -> {
inplaceModification_word_value_to_variable(name, dt, operator, memread);
inplaceModification_word_value_to_variable(name, dt, operator, memread)
}
}
}
@ -647,7 +670,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_word_litval_to_variable(name: String, dt: DataType, operator: String, value: Int) {
when (operator) {
// note: ** (power) operator requires floats.
// TODO use these + and - optimizations in the expressionAsmGenerator as well.
"+" -> {
when {
value==0 -> {}
@ -662,6 +684,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
value==0x0100 -> asmgen.out(" inc $name+1")
value==0x0200 -> asmgen.out(" inc $name+1 | inc $name+1")
value==0x0300 -> asmgen.out(" inc $name+1 | inc $name+1 | inc $name+1")
value==0x0400 -> asmgen.out(" inc $name+1 | inc $name+1 | inc $name+1 | inc $name+1")
value and 255==0 -> asmgen.out(" lda $name+1 | clc | adc #>$value | sta $name+1")
else -> asmgen.out("""
lda $name
@ -687,6 +710,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
value==0x0100 -> asmgen.out(" dec $name+1")
value==0x0200 -> asmgen.out(" dec $name+1 | dec $name+1")
value==0x0300 -> asmgen.out(" dec $name+1 | dec $name+1 | dec $name+1")
value==0x0400 -> asmgen.out(" dec $name+1 | dec $name+1 | dec $name+1 | dec $name+1")
value and 255==0 -> asmgen.out(" lda $name+1 | sec | sbc #>$value | sta $name+1")
else -> asmgen.out("""
lda $name
@ -699,41 +723,22 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
"*" -> {
if(dt == DataType.UWORD){
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
} else {
asmgen.out("""
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
}
// the mul code works for both signed and unsigned
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
} else {
if(value.absoluteValue in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
} else {
// TODO does this work for signed words? if so the uword/word distinction can be removed altogether
asmgen.out("""
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
}
asmgen.out("""
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
}
}
"/" -> {
@ -786,14 +791,63 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
""")
}
"<<" -> {
repeat(value) { asmgen.out(" asl $name | rol $name+1") }
when {
value>=16 -> asmgen.out(" lda #0 | sta $name | sta $name+1")
value==8 -> asmgen.out(" lda $name | sta $name+1 | lda #0 | sta $name")
value>2 -> asmgen.out("""
ldy #$value
- asl $name
rol $name+1
dey
bne -
""")
else -> repeat(value) { asmgen.out(" asl $name | rol $name+1") }
}
}
">>" -> {
if (value > 0) {
if(dt==DataType.UWORD) {
repeat(value) { asmgen.out(" lsr $name+1 | ror $name")}
when {
value>=16 -> asmgen.out(" lda #0 | sta $name | sta $name+1")
value==8 -> asmgen.out(" lda $name+1 | sta $name | lda #0 | sta $name+1")
value>2 -> asmgen.out("""
ldy #$value
- lsr $name+1
ror $name
dey
bne -""")
else -> repeat(value) { asmgen.out(" lsr $name+1 | ror $name")}
}
} else {
repeat(value) { asmgen.out(" lda $name+1 | asl a | ror $name+1 | ror $name") }
when {
value>=16 -> asmgen.out("""
lda $name+1
bmi +
lda #0
beq ++
+ lda #-1
+ sta $name
sta $name+1""")
value==8 -> asmgen.out("""
lda $name+1
sta $name
bmi +
lda #0
- sta $name+1
beq ++
+ lda #-1
sta $name+1
+""")
value>2 -> asmgen.out("""
ldy #$value
- lda $name+1
asl a
ror $name+1
ror $name
dey
bne -""")
else -> repeat(value) { asmgen.out(" lda $name+1 | asl a | ror $name+1 | ror $name") }
}
}
}
}
@ -917,33 +971,43 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"<<" -> {
asmgen.out("""
ldy $otherName
beq +
- asl $name
rol $name+1
dey
bne -""")
bne -
+""")
}
">>" -> {
if(dt==DataType.UWORD) {
asmgen.out("""
ldy $otherName
beq +
- lsr $name+1
ror $name
dey
bne -""")
bne -
+""")
} else {
asmgen.out("""
ldy $otherName
beq +
- lda $name+1
asl a
ror $name+1
ror $name
dey
bne -""")
bne -
+""")
}
}
"&" -> TODO("bitand (u)wordvar bytevar")
"^" -> TODO("bitxor (u)wordvar bytevar")
"|" -> TODO("bitor (u)wordvar bytevar")
"&" -> {
asmgen.out(" lda $otherName | and $name | sta $name")
if(dt in WordDatatypes)
asmgen.out(" lda #0 | sta $name+1")
}
"^" -> asmgen.out(" lda $otherName | eor $name | sta $name")
"|" -> asmgen.out(" lda $otherName | ora $name | sta $name")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -1029,7 +1093,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_word_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) {
// this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow).
println("warning: slow stack evaluation used (4): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (4): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value)
val valueDt = value.inferType(program).typeOrElse(DataType.STRUCT)
@ -1164,23 +1229,19 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
remainderWord()
}
"<<" -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
ldy P8ESTACK_LO,x
beq +
- asl $name
rol $name+1
dey
bne -
ldy P8ESTACK_LO+1,x
beq +
- asl $name
rol $name+1
dey
bne -
+""")
}
">>" -> {
asmgen.translateExpression(value)
if(dt==DataType.UWORD) {
asmgen.out("""
inx
ldy P8ESTACK_LO,x
ldy P8ESTACK_LO+1,x
beq +
- lsr $name+1
ror $name
@ -1189,8 +1250,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""") }
else {
asmgen.out("""
inx
ldy P8ESTACK_LO,x
ldy P8ESTACK_LO+1,x
beq +
- lda $name+1
asl a
@ -1201,9 +1261,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""")
}
}
"&" -> TODO("bitand (u)word (u)byte on stack")
"^" -> TODO("bitxor (u)word (u)byte on stack")
"|" -> TODO("bitor (u)word (u)byte on stack")
"&" -> {
asmgen.out(" lda P8ESTACK_LO+1,x | and $name | sta $name")
if(dt in WordDatatypes)
asmgen.out(" lda #0 | sta $name+1")
}
"^" -> asmgen.out(" lda P8ESTACK_LO+1,x | eor $name | sta $name")
"|" -> asmgen.out(" lda P8ESTACK_LO+1,x | ora $name | sta $name")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -1234,7 +1298,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: Expression, scope: Subroutine?) {
// this should be the last resort for code generation for this,
// because the value is evaluated onto the eval stack (=slow).
println("warning: slow stack evaluation used (2): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (2): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value)
asmgen.out(" jsr floats.pop_float_fac1")
asmgen.saveRegister(CpuRegister.X, false, scope)
@ -1516,7 +1581,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
else -> {
println("warning: slow stack evaluation used (6): ${mem.addressExpression::class.simpleName} at ${mem.addressExpression.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (6): ${mem.addressExpression::class.simpleName} at ${mem.addressExpression.position}") // TODO
asmgen.translateExpression(mem.addressExpression)
asmgen.out("""
jsr prog8_lib.read_byte_from_address_on_stack
@ -1585,7 +1651,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
else -> {
println("warning: slow stack evaluation used (7): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO
if(asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used (7): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO
asmgen.translateExpression(memory.addressExpression)
asmgen.out("""
jsr prog8_lib.read_byte_from_address_on_stack

View File

@ -101,7 +101,8 @@ val BuiltinFunctions = mapOf(
"rightstr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null)
FParam("length", setOf(DataType.UBYTE))), null),
"strcmp" to FSignature(false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
)
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!

View File

@ -58,7 +58,7 @@ X = BinExpr X = LeftExpr
val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf(
IAstModification.InsertBefore(assignment, firstAssign, parent),
IAstModification.InsertBefore(assignment, firstAssign, assignment.definingScope()),
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
}
}
@ -71,19 +71,10 @@ X = BinExpr X = LeftExpr
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
}
}
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope) =
if (target.identifier!=null || target.memoryAddress!=null || target.arrayindexed!=null)
target.isInRegularRAM(namespace)
else
false
}

View File

@ -1,11 +1,10 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ErrorReporter
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.IAstVisitor
@ -25,7 +24,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
@ -79,8 +78,10 @@ class CallGraph(private val program: Program) : IAstVisitor {
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val scope = directive.definingScope()
scanAssemblyCode(asm, directive, scope)
val scope = directive.definingSubroutine()
if(scope!=null) {
scanAssemblyCode(asm, directive, scope)
}
}
super.visit(directive)
@ -167,12 +168,12 @@ class CallGraph(private val program: Program) : IAstVisitor {
override fun visit(inlineAssembly: InlineAssembly) {
// parse inline asm for subroutine calls (jmp, jsr)
val scope = inlineAssembly.definingScope()
val scope = inlineAssembly.definingSubroutine()
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
super.visit(inlineAssembly)
}
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
asm.lines().forEach { line ->
val matches = asmJumpRx.matchEntire(line)
if (matches != null) {
@ -180,13 +181,15 @@ class CallGraph(private val program: Program) : IAstVisitor {
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
val node = program.namespace.lookup(jumptarget.split('.'), context)
if (node is Subroutine) {
calls[scope] = calls.getValue(scope).plus(node)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
} else if (jumptarget.contains('.')) {
// maybe only the first part already refers to a subroutine
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
if (node2 is Subroutine) {
calls[scope] = calls.getValue(scope).plus(node2)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node2)
calledBy[node2] = calledBy.getValue(node2).plus(context)
}
}
@ -199,7 +202,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
if (target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
calls[scope] = calls.getValue(scope).plus(node)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
}
}
@ -208,4 +212,55 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
}
}
fun checkRecursiveCalls(errors: ErrorReporter) {
val cycles = recursionCycles()
if(cycles.any()) {
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)
val printed = mutableSetOf<Subroutine>()
for(chain in cycles) {
if(chain[0] !in printed) {
val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" }
errors.warn("Cycle in (a subroutine call in) $chainStr", Position.DUMMY)
printed.add(chain[0])
}
}
}
}
private fun recursionCycles(): List<List<Subroutine>> {
val chains = mutableListOf<MutableList<Subroutine>>()
for(caller in calls.keys) {
val visited = calls.keys.associateWith { false }.toMutableMap()
val recStack = calls.keys.associateWith { false }.toMutableMap()
val chain = mutableListOf<Subroutine>()
if(hasCycle(caller, visited, recStack, chain))
chains.add(chain)
}
return chains
}
private fun hasCycle(sub: Subroutine, visited: MutableMap<Subroutine, Boolean>, recStack: MutableMap<Subroutine, Boolean>, chain: MutableList<Subroutine>): Boolean {
// mark current node as visited and add to recursion stack
if(recStack[sub]==true)
return true
if(visited[sub]==true)
return false
// mark visited and add to recursion stack
visited[sub] = true
recStack[sub] = true
// recurse for all neighbours
for(called in calls.getValue(sub)) {
if(hasCycle(called, visited, recStack, chain)) {
chain.add(called)
return true
}
}
// pop from recursion stack
recStack[sub] = false
return false
}
}

View File

@ -13,7 +13,7 @@ import prog8.ast.statements.VarDecl
import prog8.compiler.target.CompilationTarget
// Fix up the literal value's type to match that of the vardecl
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
internal class VarConstantValueTypeAdjuster(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@ -58,15 +58,16 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call graph for this?
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
if(decl.isArray){
if(decl.arraysize==null) {
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
val arraysize = decl.arraysize
if(arraysize==null) {
// for arrays that have no size specifier attempt to deduce the size
val arrayval = decl.value as? ArrayLiteralValue
if(arrayval!=null) {
return listOf(IAstModification.SetExpression(
@ -75,14 +76,13 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
decl
))
}
}
else if(decl.arraysize?.constIndex()==null) {
val size = decl.arraysize!!.index.constValue(program)
if(size!=null) {
return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
size, decl
))
} else if(arraysize.constIndex()==null) {
// see if we can calculate the size from other fields
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
if(cval!=null) {
arraysize.indexVar = null
arraysize.origExpression = null
arraysize.indexNum = cval
}
}
}

View File

@ -351,6 +351,13 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
// no need to check for left val constant (because of associativity)
val rnum = rightVal?.number?.toDouble()
if(rnum!=null && rnum<0.0) {
expr.operator = "-"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
return expr
}
return null
}
@ -365,12 +372,16 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when (rightConst.number.toDouble()) {
0.0 -> {
// left
return expr.left
}
val rnum = rightVal.number.toDouble()
if (rnum == 0.0) {
// left
return expr.left
}
if(rnum<0.0) {
expr.operator = "+"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
return expr
}
}
if (leftVal != null) {
@ -383,6 +394,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
}
return null
}
@ -605,6 +617,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (amount >= 16) {
return NumericLiteralValue(targetDt, 0, expr.position)
} else if (amount >= 8) {
// TODO is this correct???
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
if (amount == 8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
@ -644,8 +657,9 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
return NumericLiteralValue.optimalInteger(0, expr.position)
} else if (amount >= 8) {
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8)
return msb
if (amount == 8) {
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
}
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
}
}
@ -653,14 +667,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (amount > 16) {
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
return null
} else if (amount >= 8) {
val msbAsByte = TypecastExpression(
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
DataType.BYTE,
true, expr.position)
if (amount == 8)
return msbAsByte
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
}
}
else -> {

View File

@ -5,7 +5,7 @@ import prog8.ast.base.ErrorReporter
internal fun Program.constantFold(errors: ErrorReporter) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
val valuetypefixer = VarConstantValueTypeAdjuster(this)
valuetypefixer.visit(this)
if(errors.isEmpty()) {
valuetypefixer.applyModifications()

View File

@ -25,12 +25,12 @@ internal class StatementOptimizer(private val program: Program,
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent))
return listOf(IAstModification.Remove(block, parent as INameScope))
}
if (block !in callgraph.usedSymbols) {
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent))
return listOf(IAstModification.Remove(block, parent as INameScope))
}
}
return noModifications
@ -42,16 +42,16 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine.containsNoCodeNorVars()) {
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = callgraph.calledBy.getValue(subroutine).map {
IAstModification.Remove(it, it.parent)
IAstModification.Remove(it, it.definingScope())
}.toMutableList()
removals += IAstModification.Remove(subroutine, parent)
removals += IAstModification.Remove(subroutine, subroutine.definingScope())
return removals
}
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, parent))
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
}
return noModifications
@ -63,7 +63,7 @@ internal class StatementOptimizer(private val program: Program,
if(decl.type == VarDeclType.VAR)
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent))
return listOf(IAstModification.Remove(decl, decl.definingScope()))
}
return noModifications
@ -74,7 +74,7 @@ internal class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent))
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
}
}
@ -127,7 +127,7 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return)
return listOf(IAstModification.Remove(functionCallStatement, parent))
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
}
return noModifications
@ -150,7 +150,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
return listOf(IAstModification.Remove(ifStatement, parent))
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope()))
// empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
@ -183,12 +183,12 @@ internal class StatementOptimizer(private val program: Program,
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars()) {
errors.warn("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, parent))
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
// remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, parent))
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
}
}
@ -265,7 +265,7 @@ internal class StatementOptimizer(private val program: Program,
} else {
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
listOf(IAstModification.Remove(whileLoop, parent))
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope()))
}
}
return noModifications
@ -276,12 +276,12 @@ internal class StatementOptimizer(private val program: Program,
if(iter!=null) {
if(repeatLoop.body.containsNoCodeNorVars()) {
errors.warn("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
}
val iterations = iter.constValue(program)?.number?.toInt()
if (iterations == 0) {
errors.warn("iterations is always 0, removed loop", iter.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
}
if (iterations == 1) {
errors.warn("iterations is always 1", iter.position)
@ -308,7 +308,7 @@ internal class StatementOptimizer(private val program: Program,
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, parent))
return listOf(IAstModification.Remove(jump, jump.definingScope()))
return noModifications
}
@ -341,7 +341,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, parent))
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope()))
} else if (op2 == "-") {
// A = A +/- B - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
@ -352,7 +352,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, subConstant, parent))
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope()))
}
}
}
@ -374,7 +374,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) {
// remove assignment to self
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
}
val targetIDt = assignment.target.inferType(program, assignment)
@ -394,7 +394,7 @@ internal class StatementOptimizer(private val program: Program,
when (bexpr.operator) {
"+" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
@ -408,7 +408,7 @@ internal class StatementOptimizer(private val program: Program,
}
"-" -> {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
@ -420,18 +420,18 @@ internal class StatementOptimizer(private val program: Program,
}
}
}
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"<<" -> {
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
}
">>" -> {
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
}
}

View File

@ -20,20 +20,20 @@ internal class UnusedCodeRemover(private val program: Program, private val error
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
removals.add(IAstModification.Remove(sub, sub.definingScope()))
}
}
}
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
removals.add(IAstModification.Remove(block, block.definingScope()))
}
// remove modules that are not imported, or are empty (unless it's a library modules)
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removals.add(IAstModification.Remove(it, it.parent))
removals.add(IAstModification.Remove(it, it.definingScope()))
}
return removals
@ -91,8 +91,10 @@ internal class UnusedCodeRemover(private val program: Program, private val error
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace))
linesToRemove.add(assign1)
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
linesToRemove.add(assign1)
}
}
}

View File

@ -186,7 +186,7 @@ class TestC64Zeropage {
@Test
fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp1.available())
assertEquals(18, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
assertEquals(89, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
@ -220,7 +220,7 @@ class TestC64Zeropage {
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp.available())
assertEquals(18, zp.available())
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free
@ -273,16 +273,18 @@ class TestC64Zeropage {
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp.available())
assertEquals(18, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x9b, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0, zp.available())
}

View File

@ -41,7 +41,7 @@ of that build task, you can start the compiler with:
(You should probably make an alias...)
.. note::
.. hint::
Development and testing is done on Linux, but the compiler should run on most
operating systems. If you do have trouble building or running
the compiler on another operating system, please let me know!

View File

@ -171,6 +171,7 @@ If you're targeting the CommanderX16, there's the `x16emu <https://github.com/co
building.rst
programming.rst
syntaxreference.rst
libraries.rst
todo.rst

94
docs/source/libraries.rst Normal file
View File

@ -0,0 +1,94 @@
************************
Compiler library modules
************************
The compiler provides several "built-in" library modules with useful subroutine and variables.
Some of these may be specific for a certain compilation target, or work slightly different,
but some effort is put into making them available across compilation targets.
This means that as long as your program is only using the subroutines from these
libraries and not using hardware- and/or system dependent code, and isn't hardcoding certain
assumptions like the screen size, the exact same source program can
be compiled for multiple different target platforms. Many of the example programs that come
with Prog8 are written like this.
You can ``%import`` and use these modules explicitly, but the compiler may also import one or more
of these library modules automatically as required.
.. caution::
The resulting compiled binary program *only works on the target machine it was compiled for*.
You must recompile the program for every target you want to run it on.
syslib
------
The "system library" for your target machine. It contains many system-specific definitions such
as ROM/kernal subroutine definitions, memory location constants, and utility subroutines.
Many of these definitions overlap for the C64 and Commander X16 targets so it is still possible
to write programs that work on both targets without modifications.
conv
----
Routines to convert strings to numbers or vice versa.
- numbers to strings, in various formats (binary, hex, decimal)
- strings in decimal, hex and binary format into numbers
textio (txt.*)
--------------
This will probably be the most used library module. It contains a whole lot of routines
dealing with text-based input and output (to the screen). Such as
- printing strings and numbers
- reading text input from the user via the keyboard
- filling or clearing the screen and colors
- scrolling the text on the screen
- placing individual characters on the screen
diskio
------
Provides several routines that deal with disk drive I/O, such as:
- show directory
- display disk drive status
- load and save data from and to the disk
- delete and rename files on the disk
floats
------
Provides definitions for the ROM/kernel subroutines and utility routines dealing with floating
point variables. This includes ``print_f``, the routine used to print floating point numbers.
graphics
--------
High-res monochrome bitmap graphics routines:
- clearing the screen
- drawing lines
- drawing circles and discs (filled circles)
- plotting individual pixels
math
----
Low level math routines. You should not normally have to bother with this directly.
The compiler needs it to implement most of the math operations in your programs.
cx16logo
--------
A 'fun' module that contains the Commander X16 logo and that allows you
to print it anywhere on the screen.
prog8_lib
---------
Low level language support. You should not normally have to bother with this directly.
The compiler needs it for verious built-in system routines.

View File

@ -260,6 +260,7 @@ Note that the various keywords for the data type and variable type (``byte``, ``
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance.
It's possible to assign a new array to another array, this will overwrite all elements in the original
array with those in the value array. The number and types of elements have to match.
For large arrays this is a slow operation because every element is copied over. It should probably be avoided.
@ -307,7 +308,7 @@ as newlines, quote characters themselves, and so on. The ones used most often ar
read the syntax reference on strings.
.. note::
.. hint::
Strings and uwords (=memory address) can often be interchanged.
An array of strings is actually an array of uwords where every element is the memory
address of the string. You can pass a memory address to assembly functions
@ -372,13 +373,6 @@ address you specified, and setting the varible will directly modify that memory
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
.. note::
Directly accessing random memory locations is not yet supported without the
intermediate step of declaring a memory-mapped variable for the memory location.
The advantages of this however, is that it's clearer what the memory location
stands for, and the compiler also knows the data type.
Converting types into other types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -656,23 +650,20 @@ There's a set of predefined functions in the language. These are fixed and can't
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
sin(x)
Sine. (floating point version)
Math
^^^^
abs(x)
Absolute value.
atan(x)
Arctangent.
ceil(x)
Rounds the floating point up to an integer towards positive infinity.
cos(x)
Cosine. (floating point version)
sin8u(x)
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
sin8(x)
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
sin16u(x)
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
sin16(x)
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
Cosine. (floating point version)
cos8u(x)
Fast 8-bit ubyte cosine of angle 0..255, result is in range 0..255
@ -686,14 +677,11 @@ cos16u(x)
cos16(x)
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
abs(x)
Absolute value.
deg(x)
Radians to degrees.
tan(x)
Tangent.
atan(x)
Arctangent.
floor (x)
Rounds the floating point down to an integer towards minus infinity.
ln(x)
Natural logarithm (base e).
@ -701,45 +689,48 @@ ln(x)
log2(x)
Base 2 logarithm.
rad(x)
Degrees to radians.
round(x)
Rounds the floating point to the closest integer.
sin(x)
Sine. (floating point version)
sgn(x)
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
sin8u(x)
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
sin8(x)
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
sin16u(x)
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
sin16(x)
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
sqrt16(w)
16 bit unsigned integer Square root. Result is unsigned byte.
sqrt(x)
Floating point Square root.
round(x)
Rounds the floating point to the closest integer.
tan(x)
Tangent.
floor (x)
Rounds the floating point down to an integer towards minus infinity.
ceil(x)
Rounds the floating point up to an integer towards positive infinity.
Array operations
^^^^^^^^^^^^^^^^
rad(x)
Degrees to radians.
any(x)
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
deg(x)
Radians to degrees.
max(x)
Maximum of the values in the array value x
min(x)
Minimum of the values in the array value x
sum(x)
Sum of the values in the array value x
sort(array)
Sort the array in ascending order (in-place)
Note: sorting a floating-point array is not supported right now, as a general sorting routine for this will
be extremely slow. Either build one yourself or find another solution that doesn't require sorting
floating point values.
reverse(array)
Reverse the values in the array (in-place).
Can be used after sort() to sort an array in descending order.
all(x)
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
len(x)
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
@ -748,15 +739,80 @@ len(x)
length of the string during execution, the value of len(string) may no longer be correct!
(use strlen function if you want to dynamically determine the length)
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
max(x)
Maximum of the values in the array value x
min(x)
Minimum of the values in the array value x
reverse(array)
Reverse the values in the array (in-place).
Can be used after sort() to sort an array in descending order.
sum(x)
Sum of the values in the array value x
sort(array)
Sort the array in ascending order (in-place)
Supported are arrays of bytes or word values.
Sorting a floating-point array is not supported right now, as a general sorting routine for this will
be extremely slow. Either build one yourself or find another solution that doesn't require sorting.
Finally, note that sorting an array with strings in it will not do what you might think;
it considers the array as just an array of integer words and sorts the string *pointers* accordingly.
Sorting strings alphabetically has to be programmed yourself if you need it.
Strings and memory blocks
^^^^^^^^^^^^^^^^^^^^^^^^^
memcopy(from, to, numbytes)
Efficiently copy a number of bytes (1 - 256) from a memory location to another.
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not.
The most efficient will always be to write a specialized copy routine in assembly yourself!
memset(address, numbytes, bytevalue)
Efficiently set a part of memory to the given (u)byte value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
Note that for clearing the character screen, very fast specialized subroutines are
available in the ``txt`` block (part of the ``textio`` module)
memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
leftstr(source, target, length)
Copies the left side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rightstr(source, target, length)
Copies the right side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
strlen(str)
Number of bytes in the string. This value is determined during runtime and counts upto
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
Don't confuse this with ``len`` and ``sizeof``
strcmp(string1, string2)
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
Note that you can also directly compare strings and string values with eachother
using ``==``, ``<`` etcetera (it will use strcmp for you under water automatically).
substr(source, target, start, length)
Copies a segment from the source string, starting at the given index,
and of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
Miscellaneous
^^^^^^^^^^^^^
exit(returncode)
Immediately stops the program and exits it, with the returncode in the A register.
Note: custom interrupt handlers remain active unless manually cleared first!
lsb(x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
@ -764,19 +820,10 @@ lsb(x)
msb(x)
Get the most significant byte of the word x.
sgn(x)
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
mkword(msb, lsb)
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
So mkword($80, $22) results in $8022.
any(x)
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
all(x)
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
rnd()
returns a pseudo-random byte from 0..255
@ -810,51 +857,6 @@ ror2(x)
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
memcopy(from, to, numbytes)
Efficiently copy a number of bytes (1 - 256) from a memory location to another.
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not.
The most efficient will always be to write a specialized copy routine in assembly yourself!
memset(address, numbytes, bytevalue)
Efficiently set a part of memory to the given (u)byte value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
Note that for clearing the character screen, very fast specialized subroutines are
available in the ``txt`` block (part of the ``textio`` module)
memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
leftstr(source, target, length)
Copies the left side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rightstr(source, target, length)
Copies the right side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
substr(source, target, start, length)
Copies a segment from the source string, starting at the given index,
and of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way.
set_carry() / clear_carry()
Set (or clear) the CPU status register Carry flag. No result value.
(translated into ``SEC`` or ``CLC`` cpu instruction)
set_irqd() / clear_irqd()
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction)
rsave()
Saves the CPU registers and the status flags.
You can now more or less 'safely' use the registers directly, until you
@ -869,10 +871,22 @@ rrestore()
read_flags()
Returns the current value of the CPU status register.
exit(returncode)
Immediately stops the program and exits it, with the returncode in the A register.
Note: custom interrupt handlers remain active unless manually cleared first!
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
set_carry() / clear_carry()
Set (or clear) the CPU status register Carry flag. No result value.
(translated into ``SEC`` or ``CLC`` cpu instruction)
set_irqd() / clear_irqd()
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction)
swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way.
Library routines

View File

@ -16,8 +16,9 @@ Currently there are two machines that are supported as compiler target (selectab
This chapter explains the relevant system details of these machines.
.. note::
If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
.. hint::
If you only use standard kernel and prog8 library routines,
it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
Memory Model

Binary file not shown.

Binary file not shown.

View File

@ -166,7 +166,7 @@ main {
else
c64.SPRPTR[i] = $2000/64 ; small ball
c64.SPCOL[i] = spritecolors[(zc>>13) as byte + 4] ; further away=darker color
c64.SPCOL[i] = spritecolors[(zc>>13) as ubyte + 4] ; further away=darker color
}
}
}

17
examples/cxlogo.p8 Normal file
View File

@ -0,0 +1,17 @@
%import textio
%import cx16logo
; Note: this program is compatible with C64 and CX16.
main {
sub start() {
repeat {
ubyte col = rnd() % (txt.DEFAULT_WIDTH-12) + 3
ubyte row = rnd() % (txt.DEFAULT_HEIGHT-7)
cx16logo.logo_at(col, row)
txt.plot(col-3, row+7 )
txt.print("commander x16")
}
}
}

View File

@ -1,15 +0,0 @@
%target c64
%import textio
%import syslib
%zeropage basicsafe
%option no_sysinit
%launcher none
%address 50000
; This example shows the directory contents of disk drive 8.
; You load it with LOAD "diskdir-sys50000",8,1
; and then call it with SYS 50000.
; The only difference with diskdir.p8 is the directives that make this load at 50000.
%import diskdir

View File

@ -1,59 +0,0 @@
%import textio
%import syslib
%option no_sysinit
%zeropage basicsafe
; This example shows the directory contents of disk drive 8.
; Note: this program is compatible with C64 and CX16.
main {
sub start() {
txt.print("directory of disk drive #8:\n\n")
diskdir(8)
}
sub diskdir(ubyte drivenumber) {
c64.SETNAM(1, "$")
c64.SETLFS(1, drivenumber, 0)
void c64.OPEN() ; open 1,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(1) ; use #1 as input channel
if_cs
goto io_error
repeat 4 {
void c64.CHRIN() ; skip the 4 prologue bytes
}
; while not key pressed / EOF encountered, read data.
ubyte status = c64.READST()
while not status {
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
txt.chrout(' ')
ubyte @zp char
do {
char = c64.CHRIN()
txt.chrout(char)
} until char==0
txt.chrout('\n')
void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN()
status = c64.READST()
void c64.STOP()
if_nz
break
}
io_error:
status = c64.READST()
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(1)
if status and status != 64 { ; 64=end of file
txt.print("\ni/o error, status: ")
txt.print_ub(status)
txt.chrout('\n')
}
}
}

View File

@ -218,7 +218,7 @@ waitkey:
if linepos and blocklogic.isLineFull(linepos)
blocklogic.collapse(linepos)
lines += num_lines
uword[] scores = [10, 25, 50, 100] ; can never clear more than 4 lines
uword[] scores = [10, 25, 50, 100] ; can never clear more than 4 lines at once
score += scores[num_lines-1]
speedlevel = 1+lsb(lines/10)
drawScore()

View File

@ -1,13 +1,24 @@
%import textio
%import syslib
%import conv
%import floats
%zeropage basicsafe
main {
sub start() {
uword[] array = [1, 2, 3]
ubyte ii = 0
ubyte ii2 = ii+2
array[ii+1] = array[ii2] ; TODO fix overwriting the single array index autovar
uword xx
for xx in array {
txt.print_uw(xx)
txt.chrout('\n')
}
testX()
}
asmsub testX() {
@ -25,6 +36,3 @@ _saveX .byte 0
}}
}
}

View File

@ -83,20 +83,20 @@ main {
uw=muwarray[bb]
fl=mflarray[bb]
A=s1[bb*3]
ub=s1[bb*3]
bb=barray[bb*3]
ub=ubarray[bb*3]
ww=warray[bb*3]
uw=uwarray[bb*3]
fl=flarray[bb*3]
A=ms1[bb*3]
ub=ms1[bb*3]
bb=mbarray[bb*3]
ub=mubarray[bb*3]
ww=mwarray[bb*3]
uw=muwarray[bb*3]
fl=mflarray[bb*3]
; A=s1[bb*3]
; ub=s1[bb*3]
; bb=barray[bb*3]
; ub=ubarray[bb*3]
; ww=warray[bb*3]
; uw=uwarray[bb*3]
; fl=flarray[bb*3]
; A=ms1[bb*3]
; ub=ms1[bb*3]
; bb=mbarray[bb*3]
; ub=mubarray[bb*3]
; ww=mwarray[bb*3]
; uw=muwarray[bb*3]
; fl=mflarray[bb*3]
; write array
barray[2]++
@ -132,11 +132,11 @@ main {
uwarray[bb] = uw
flarray[bb] = fl
s1[bb*3] = ub
barray[bb*3] = bb
ubarray[bb*3] = ub
warray[bb*3] = ww
uwarray[bb*3] = uw
flarray[bb*3] = fl
; s1[bb*3] = ub
; barray[bb*3] = bb
; ubarray[bb*3] = ub
; warray[bb*3] = ww
; uwarray[bb*3] = uw
; flarray[bb*3] = fl
}
}

View File

@ -1,5 +1,6 @@
%import textio
%import conv
%import diskio
%option no_sysinit
%zeropage basicsafe
@ -8,9 +9,6 @@
; 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
@ -20,10 +18,9 @@ main {
sub start() {
txt.lowercase()
txt.print("\u000c\n --- TextElite v1.0 ---\n")
txt.print("\u000c\n --- TextElite v1.1 ---\n")
galaxy.init(1)
galaxy.travel_to(numforLave)
galaxy.travel_to(1, numforLave)
market.init(0) ; Lave's market is seeded with 0
ship.init()
planet.display(false)
@ -64,15 +61,73 @@ main {
}
trader {
str Savegame = "↑commander.save"
str input = "??????????"
ubyte num_chars
struct SaveData {
ubyte galaxy
ubyte planet
ubyte cargo0
ubyte cargo1
ubyte cargo2
ubyte cargo3
ubyte cargo4
ubyte cargo5
ubyte cargo6
ubyte cargo7
ubyte cargo8
ubyte cargo9
ubyte cargo10
ubyte cargo11
ubyte cargo12
ubyte cargo13
ubyte cargo14
ubyte cargo15
ubyte cargo16
uword cash
ubyte max_cargo
ubyte fuel
}
SaveData savedata
sub do_load() {
txt.print("\nTODO LOAD\n")
txt.print("\nLoading universe...")
if diskio.load(8, Savegame, &savedata) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
diskio.status(8)
txt.chrout('\n')
return
}
ship.cash = savedata.cash
ship.Max_cargo = savedata.max_cargo
ship.fuel = savedata.fuel
memcopy(&savedata.cargo0, ship.cargohold, len(ship.cargohold))
galaxy.travel_to(savedata.galaxy, savedata.planet)
planet.display(false)
}
sub do_save() {
txt.print("\nTODO SAVE\n")
savedata.galaxy = galaxy.number
savedata.planet = planet.number
savedata.cash = ship.cash
savedata.max_cargo = ship.Max_cargo
savedata.fuel = ship.fuel
memcopy(ship.cargohold, &savedata.cargo0, len(ship.cargohold))
txt.print("\nSaving universe...")
diskio.delete(8, Savegame)
if diskio.save(8, Savegame, &savedata, sizeof(savedata)) {
txt.print("ok\n")
} else {
txt.print("\ni/o error: ")
diskio.status(8)
txt.chrout('\n')
}
}
sub do_jump() {
@ -106,7 +161,7 @@ trader {
} else {
txt.print(" Not found!\n")
}
galaxy.travel_to(current_planet)
galaxy.travel_to(galaxy.number, current_planet)
}
sub do_buy() {
@ -190,8 +245,7 @@ trader {
}
sub do_next_galaxy() {
galaxy.nextgalaxy()
galaxy.travel_to(planet.number)
galaxy.travel_to(galaxy.number+1, planet.number)
planet.display(false)
}
@ -205,7 +259,7 @@ trader {
} else {
txt.print(" Not found!")
}
galaxy.travel_to(current_planet)
galaxy.travel_to(galaxy.number, current_planet)
} else {
planet.display(false)
}
@ -257,7 +311,6 @@ market {
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
@ -303,7 +356,11 @@ market {
util.print_10s(current_price[ci])
txt.print(" ")
txt.print_ub(current_quantity[ci])
txt.print(unitnames[units[ci]])
when units[ci] {
0 -> txt.chrout('t')
1 -> txt.print("kg")
2 -> txt.chrout('g')
}
txt.print(" ")
txt.print_ub(ship.cargohold[ci])
txt.chrout('\n')
@ -348,8 +405,8 @@ galaxy {
number = 1
}
sub travel_to(ubyte system) {
init(number)
sub travel_to(ubyte galaxynum, ubyte system) {
init(galaxynum)
generate_next_planet() ; always at least planet 0 (separate to avoid repeat ubyte overflow)
repeat system {
generate_next_planet()
@ -386,9 +443,9 @@ galaxy {
}
if found
travel_to(current_closest_pi)
travel_to(number, current_closest_pi)
else
travel_to(current_planet_num)
travel_to(number, current_planet_num)
return found
}
@ -421,7 +478,7 @@ galaxy {
pn++
} until pn==0
travel_to(current_planet)
travel_to(number, current_planet)
}
ubyte pn_pair1
@ -448,6 +505,7 @@ galaxy {
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++
@ -647,8 +705,9 @@ planet {
name[nx] = pairs0[x]
nx++
}
if pairs0[x+1] != '.' {
name[nx] = pairs0[x+1]
x++
if pairs0[x] != '.' {
name[nx] = pairs0[x]
nx++
}
}
@ -720,8 +779,7 @@ planet {
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
recursive_soup() ; RECURSIVE CALL - ignore the warning message from the compiler; we don't use local variables or parameters so we're safe in this case
stack_ptr--
source_ptr = source_stack[stack_ptr]
} else {
@ -892,13 +950,13 @@ util {
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
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 +

View File

@ -3,6 +3,7 @@
%import graphics
%zeropage floatsafe
main {
sub start() {