Compare commits

...

126 Commits

Author SHA1 Message Date
e6079dfd71 don't always use pha/pla in pointer expression code 2021-02-27 16:21:46 +01:00
2b435fe6a5 vtui example updated to vtui 0.6 2021-02-27 03:30:21 +01:00
4e640b11fd added kernal bank switch trick to rasterbars 2021-02-26 01:16:06 +01:00
8b1e1e68fa switch to Kotlin's new JVM IR compilation 2021-02-26 01:10:00 +01:00
fd11927708 optimized highres 4c position calc a bit 2021-02-26 00:43:51 +01:00
cd500fee8c wording 2021-02-25 00:52:27 +01:00
1bd32c0f19 added animal guessing game example 2021-02-24 22:58:16 +01:00
7aefca3de0 target 2021-02-24 00:17:52 +01:00
f275ed96ea optimized palette.set_color() 2021-02-24 00:01:27 +01:00
d14dac3872 got rid of final traces of heapid, fixed compiler warnings 2021-02-24 00:01:04 +01:00
b0213b0565 vtui lib 2021-02-23 23:31:32 +01:00
c677f0a875 fixed string interning to also consider the alt-encoding 2021-02-23 23:27:44 +01:00
6e65cb2c0a added sounds to cx16 tehtriz 2021-02-23 01:29:45 +01:00
e65c5402d7 added cx16 rasterbars example 2021-02-22 02:11:44 +01:00
334f86480a added irq routines for cx16 2021-02-22 00:48:41 +01:00
0e62f5b759 don't remove subroutines in a block marked with "force_output" 2021-02-21 23:25:26 +01:00
edf9a500d3 kernel -> kernal 2021-02-21 22:48:06 +01:00
001d01fdaf slight tweak to 64tass .cpu to enable wdc65c02 variant on cx16 with its extra opcodes 2021-02-21 22:45:23 +01:00
a95677564e changed system irq/rasterirq setting routines 2021-02-21 22:23:50 +01:00
4aca8bb8df also track subroutines in the callgraph that only get their address taken 2021-02-21 22:09:49 +01:00
5540482888 compiler error for duplicate when choice labels 2021-02-21 21:26:15 +01:00
00d735249b fix pointer write outside zeropage 2021-02-21 16:22:44 +01:00
b5289511ba don't remove empty when choice from the list of choices! 2021-02-21 15:11:19 +01:00
b6ded8501f added 'align_word' and 'align_page' block options to control block start address alignment in the assembler 2021-02-21 01:24:44 +01:00
781915d2cf reducing dependencies 2021-02-20 17:54:33 +01:00
f4cef3eaf2 reducing dependencies 2021-02-20 17:19:54 +01:00
d23c2eed86 test 2021-02-20 16:58:24 +01:00
15695a304e start address of blocks without explicit memory address, is now word-aligned in memory 2021-02-20 03:06:00 +01:00
6319269976 underscore '_' is now also mapped to petscii, to the graphical symbol 2021-02-20 02:55:06 +01:00
0ed3d951a7 don't require carry parameter Pc to asmsubs to be last 2021-02-20 02:27:57 +01:00
2aa39757b4 reduce dependencies on global compilationtarget 2021-02-19 19:02:29 +01:00
39d32a3600 refactor cpuCheck 2021-02-19 18:48:12 +01:00
219d17de34 reduce dependencies on global compilaiontarget 2021-02-19 18:33:54 +01:00
9bb5b454e4 reduce dependencies on global compilaiontarget 2021-02-18 23:44:26 +01:00
2412f8c531 added cx16 vtui example 2021-02-18 23:16:38 +01:00
8701d684e6 added cx16 vtui example 2021-02-18 03:45:06 +01:00
b543cc34cd no longer warn about removing unused asmsubs 2021-02-18 01:52:56 +01:00
791dbbab9b fixed block label itself not getting the correct memory address in the assembly
fixed %asmbinary relative path issues
2021-02-18 01:28:33 +01:00
ac0b1da3fc machinedefinition doesn't import system libs itself anymore 2021-02-18 00:43:32 +01:00
2f97aedc3c fixed invalid removal of string tag from memory() 2021-02-16 23:58:31 +01:00
ab544ee965 improved string constant interning; no longer output duplicate strings in the Ast 2021-02-16 23:43:38 +01:00
fa527f8624 restored optimization of txt.print() with strings of lengths 1 or 2 2021-02-16 23:37:11 +01:00
92ee0aefee docs: replaced old invalid c64scr names with txt 2021-02-16 23:28:35 +01:00
99759ae853 enhanced tehtriz blocks to have light edges 2021-02-15 17:48:10 +01:00
81930312ff added textio.setcc2() on commanderX16 to enable setting fg+bg colors. 2021-02-15 17:47:48 +01:00
194fbcdd91 todos 2021-02-15 04:41:33 +01:00
1e3930aae2 fix bug in evaluating logical expressions if one of the operands was not boolean 1 or 0 2021-02-14 18:29:05 +01:00
62dda4d891 fix asm bug in conv.any2uword 2021-02-14 17:13:56 +01:00
2b870fb9f7 get rid of compiled examples. Just compile them yourself... 2021-02-14 17:13:29 +01:00
53f0318187 version 6.1 2021-02-14 00:07:45 +01:00
5e6e711f33 optimize pokew() 2021-02-14 00:05:57 +01:00
78af2cd4dc optimize peekw() 2021-02-13 23:52:08 +01:00
02cb237623 added poke() and pokew() builtin functions 2021-02-13 23:16:50 +01:00
cc0f19653e added peek() and peekw() builtin functions 2021-02-13 22:38:39 +01:00
4fff150c7b fixed mkword() bug 2021-02-13 22:00:13 +01:00
f6136891cc optimized for loop over const bytes, fixed downto 1 2021-02-13 13:46:02 +01:00
1e22170302 added graphical starmaps to textelite 2021-02-11 00:23:36 +01:00
bdda6f502a textelite output cleanups and alignments 2021-02-10 23:19:07 +01:00
1bbd77fddb added txt.column() 2021-02-10 22:47:49 +01:00
321fdd10d1 ported tehtriz to Cx16 2021-02-10 21:55:14 +01:00
9867dfcdeb ported tehtriz to Cx16 2021-02-10 21:44:35 +01:00
7c09ac632c got rid of the --longOptionNames in the cli argparser 2021-02-10 21:26:46 +01:00
3502f65332 reducing dependencies 2021-02-09 19:03:21 +01:00
628390c3b5 reducing dependencies 2021-02-09 18:56:47 +01:00
bc37097df2 reducing dependencies 2021-02-09 18:49:25 +01:00
7d98275763 reducing dependencies 2021-02-09 02:06:27 +01:00
d6ffb549f6 reducing dependencies 2021-02-09 01:47:05 +01:00
bcd0db984d reducing ast dependencies - moved ErrorReporter back to compiler module 2021-02-09 01:15:31 +01:00
d9244f22c2 reducing ast dependencies - separate Ast compilation module 2021-02-09 01:06:11 +01:00
c97d76dbf2 reducing ast dependencies 2021-02-09 00:05:56 +01:00
9e05e97d7f reducing ast dependencies 2021-02-07 19:38:20 +01:00
1070dedd7c todo 2021-02-07 19:08:47 +01:00
ccd1516637 reducing ast dependencies 2021-02-07 18:44:38 +01:00
f1f51a01c6 reducing ast dependencies 2021-02-07 18:34:55 +01:00
be75b8dbe5 reducing ast dependencies 2021-02-07 07:05:00 +01:00
02fae0e722 reducing ast dependencies 2021-02-07 06:50:59 +01:00
e35b739579 reducing ast dependencies 2021-02-07 06:39:08 +01:00
34aa6cc8a2 compiler checks for conflicting register usage in sub arguments vs target parameter registers 2021-02-07 05:25:50 +01:00
d7a6b20028 todo 2021-02-07 01:14:10 +01:00
eb2d5bb1f8 fix bank arg error in gfx2.position 2021-02-06 16:58:17 +01:00
cefef3d1be todo 2021-02-06 15:22:31 +01:00
cc96ab7a9b assignment source now also treats cx16.r[0-15] as registers
no longer create useless assignment code for r0=r0
2021-02-06 13:01:45 +01:00
49ea31c0a4 fix shift signed word right 2021-02-06 01:23:31 +01:00
f1478d776b fix vertical line highres 4color 2021-02-05 18:09:21 +01:00
40e4cfb686 amiga 2021-02-04 17:47:52 +01:00
76f459ee95 amiga 2021-02-02 23:09:03 +01:00
c478718019 fixed and optimized horiz_line for highres 4c 2021-02-01 22:03:10 +01:00
c27248a58b amiga 2021-01-29 23:52:29 +01:00
51bc539468 added palette.set_rgb() 2021-01-29 02:46:07 +01:00
2395863e7e asmsubs: fix clobbering and optimize register usage for loading the arguments 2021-01-29 01:52:49 +01:00
69c459c8ac gfx2 highres 4colors 2021-01-28 22:28:14 +01:00
c8855b2b10 better error msg 2021-01-27 02:40:56 +01:00
a910c0fddb gfx2 highres 4colors 2021-01-27 02:31:20 +01:00
fd55611cac asmsubs: don't use stack for simple lsb/msb/mkword arguments 2021-01-27 01:41:55 +01:00
52f6be2bb0 gfx2: changed screen mode numbering to a more intuitive sequence 2021-01-26 18:17:20 +01:00
857f930dc2 amiga 2021-01-26 00:09:42 +01:00
dd2c436dc6 tweaked repeat 2021-01-25 23:39:54 +01:00
9f047ba752 palette.set_monochrome() now has 2 arguments: screen and draw color RGB values 2021-01-24 04:15:15 +01:00
9d4ec4a9b2 syntaxfile 2021-01-24 00:42:26 +01:00
cdc6d9aa65 moved cx16 imageviewer into its own git repo. Version 6.0. 2021-01-23 23:49:17 +01:00
997bc21feb added offsetof() to get the byte offset of struct members. 2021-01-23 23:11:57 +01:00
975af4764d remove no longer needed strlen() calls from diskio routines 2021-01-23 22:46:46 +01:00
bf69219f98 allow uwordpointer[index] syntax as equivalent to @(uwordpointer+index) index can be >255 here! 2021-01-23 22:39:30 +01:00
f34f9329f1 fixed bug in memcopy 2021-01-23 19:49:53 +01:00
90271d0dcd textelite was okay 2021-01-23 19:01:02 +01:00
195cd7597d fix pointer-to-pointer assignment 2021-01-23 18:50:46 +01:00
4a81406262 fix diskio rename() and delete() 2021-01-23 17:57:30 +01:00
f9fd426843 Merge branch 'pointer-index-optimize'
# Conflicts:
#	docs/source/todo.rst
2021-01-23 15:57:23 +01:00
e612056ecd more optimal screen pointer access in plasma.p8 example 2021-01-23 15:54:18 +01:00
6f0103398b fix Y register clobbering in pointer access code 2021-01-23 15:24:41 +01:00
afb60db382 todo 2021-01-20 18:43:08 +01:00
5731b876ff textelite save bug found 2021-01-20 01:36:46 +01:00
055f917a2e fixed missing code for certain memread expressions when casted to uword 2021-01-20 01:30:11 +01:00
4ed7fb771c started pointer access optimization 2021-01-20 00:17:33 +01:00
c328e9018c cx16 assembler was moved into its own github repo 2021-01-18 01:38:33 +01:00
b270f6f713 added cx16.rombank() and rambank(). Select kernal rom in i/o heavy programs for faster disk i/o 2021-01-17 19:16:21 +01:00
5c13918f11 cx16 reset_system() bank selection change 2021-01-17 18:28:43 +01:00
40cc216557 optimize pointer var access if var is already on zeropage 2021-01-16 18:31:37 +01:00
1481f92cb0 optimize memory read expression of ptr + constant index 2021-01-16 17:41:15 +01:00
76d54fbe5c optimize assignment to memory pointer with fixed byte offset 2021-01-15 20:46:47 +01:00
9f72779cdc optimize assignment from memory pointer with fixed byte offset 2021-01-15 20:09:13 +01:00
3dcef89a74 optimize (zp),y instructions for 65c02 to use (zp) 2021-01-15 19:14:35 +01:00
46373717b6 get rid of unused ci image format reader 2021-01-15 18:29:25 +01:00
7277c08fa6 added textio.spc(). assem tweaks. 2021-01-14 22:51:09 +01:00
04e75455c4 assem tweaks 2021-01-14 21:07:06 +01:00
8ac17ae14e fix assem parsing of 4 letter instructions 2021-01-14 18:41:29 +01:00
171 changed files with 5071 additions and 5646 deletions

View File

@ -3,14 +3,11 @@
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true"> <inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages> <Languages>
<language minSize="100" isEnabled="false" name="JavaScript" />
<language isEnabled="false" name="Groovy" />
<language isEnabled="false" name="Style Sheets" />
<language minSize="70" name="Kotlin" /> <language minSize="70" name="Kotlin" />
<language isEnabled="false" name="TypeScript" /> <language isEnabled="false" name="Groovy" />
<language isEnabled="false" name="ActionScript" />
</Languages> </Languages>
</inspection_tool> </inspection_tool>
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true"> <inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" /> <option name="processCode" value="false" />
<option name="processLiterals" value="true" /> <option name="processLiterals" value="true" />

2
.idea/kotlinc.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Kotlin2JvmCompilerArguments"> <component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="1.8" /> <option name="jvmTarget" value="11" />
</component> </component>
</project> </project>

1
.idea/modules.xml generated
View File

@ -3,6 +3,7 @@
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" /> <module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" /> <module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" /> <module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" /> <module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />

View File

@ -52,7 +52,7 @@ What does Prog8 provide?
- "c64": Commodore-64 (6510 CPU = almost a 6502) - "c64": Commodore-64 (6510 CPU = almost a 6502)
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) - "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
- 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)! - If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!

View File

@ -1,7 +1,7 @@
plugins { plugins {
id 'java' id 'java'
id 'application' id 'application'
id "org.jetbrains.kotlin.jvm" version "1.4.21" id "org.jetbrains.kotlin.jvm" version "1.4.30"
id 'org.jetbrains.dokka' version "0.9.18" id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '6.1.0' id 'com.github.johnrengelman.shadow' version '6.1.0'
} }
@ -18,14 +18,12 @@ repositories {
def prog8version = rootProject.file('compiler/res/version.txt').text.trim() def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies { dependencies {
implementation project(':parser') implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect" // implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.antlr:antlr4-runtime:4.8'
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1' implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
// implementation 'net.razorvine:ksim65:1.8' // implementation 'net.razorvine:ksim65:1.8'
// implementation "com.github.hypfvieh:dbus-java:3.2.4" // implementation "com.github.hypfvieh:dbus-java:3.2.4"
implementation project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
@ -36,6 +34,7 @@ dependencies {
compileKotlin { compileKotlin {
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
useIR = true
// verbose = true // verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference" // freeCompilerArgs += "-XXLanguage:+NewInference"
} }
@ -44,6 +43,7 @@ compileKotlin {
compileTestKotlin { compileTestKotlin {
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
useIR = true
} }
} }

View File

@ -11,9 +11,8 @@
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" /> <orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" /> <orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" /> <orderEntry type="library" name="unittest-libs" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" /> <orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
<orderEntry type="library" name="antlr-runtime-4.9" level="project" /> <orderEntry type="module" module-name="compilerAst" />
</component> </component>
</module> </module>

View File

@ -34,6 +34,7 @@ graphics {
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) { sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; Bresenham algorithm. ; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations. ; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; TODO there are some slight errors at the first/last pixels in certain slopes...??
if y1>y2 { if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases ; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2) swap(x1, x2)

View File

@ -11,7 +11,7 @@ c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte &ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte &ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec &ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $90 ; kernel status variable for I/O &ubyte STATUS = $90 ; kernal status variable for I/O
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ) &ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ) &ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
@ -178,7 +178,7 @@ c64 {
; ---- C64 ROM kernal routines ---- ; ---- C64 ROM kernal routines ----
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead) romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
@ -294,6 +294,12 @@ asmsub init_system() {
}} }}
} }
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the C64
}}
}
asmsub disable_runstop_and_charsetswitch() clobbers(A) { asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{ %asm {{
lda #$80 lda #$80
@ -304,27 +310,13 @@ asmsub disable_runstop_and_charsetswitch() clobbers(A) {
}} }}
} }
asmsub set_irqvec_excl() clobbers(A) { asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sei
lda #<_irq_handler
sta c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
asmsub set_irqvec() clobbers(A) {
%asm {{ %asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei sei
lda #<_irq_handler lda #<_irq_handler
sta c64.CINV sta c64.CINV
@ -333,9 +325,23 @@ asmsub set_irqvec() clobbers(A) {
cli cli
rts rts
_irq_handler jsr _irq_handler_init _irq_handler jsr _irq_handler_init
jsr irq.irq _modified jsr $ffff ; modified
jsr _irq_handler_end jsr _irq_handler_end
jmp c64.IRQDFRT ; continue with normal kernel irq routine lda _use_kernal
bne +
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init _irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine ; save all zp scratch registers and the X register as these might be clobbered by the irq routine
@ -388,7 +394,7 @@ IRQ_SCRATCH_ZPWORD2 .word 0
}} }}
} }
asmsub restore_irqvec() clobbers(A) { asmsub restore_irq() clobbers(A) {
%asm {{ %asm {{
sei sei
lda #<c64.IRQDFRT lda #<c64.IRQDFRT
@ -404,8 +410,15 @@ asmsub restore_irqvec() clobbers(A) {
}} }}
} }
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) { asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
%asm {{ %asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei sei
jsr _setup_raster_irq jsr _setup_raster_irq
lda #<_raster_irq_handler lda #<_raster_irq_handler
@ -416,12 +429,21 @@ asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
rts rts
_raster_irq_handler _raster_irq_handler
jsr set_irqvec._irq_handler_init jsr set_irq._irq_handler_init
jsr irq.irq _modified jsr $ffff ; modified
jsr set_irqvec._irq_handler_end jsr set_irq._irq_handler_end
lda #$ff lda #$ff
sta c64.VICIRQ ; acknowledge raster irq sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFRT lda set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq _setup_raster_irq
pha pha
@ -445,28 +467,6 @@ _setup_raster_irq
}} }}
} }
asmsub set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) {
%asm {{
sei
jsr set_rasterirq._setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
; ---- end of C64 specific system utility routines ---- ; ---- end of C64 specific system utility routines ----
} }
@ -500,19 +500,23 @@ sys {
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) { asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
%asm {{ %asm {{
ldx cx16.r0 ldx cx16.r0
stx P8ZP_SCRATCH_W1 stx P8ZP_SCRATCH_W1 ; source in ZP
ldx cx16.r0+1 ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1 stx P8ZP_SCRATCH_W1+1
ldx cx16.r1 ldx cx16.r1
stx P8ZP_SCRATCH_W2 stx P8ZP_SCRATCH_W2 ; target in ZP
ldx cx16.r1+1 ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1 stx P8ZP_SCRATCH_W2+1
cpy #0 cpy #0
bne _longcopy bne _longcopy
; copy <= 255
tay
_remainder ; copy <= 255 bytes
tay
bne _copyshort
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda P8ZP_SCRATCH_W1 lda P8ZP_SCRATCH_W1
bne + bne +
dec P8ZP_SCRATCH_W1+1 dec P8ZP_SCRATCH_W1+1
@ -521,18 +525,19 @@ _remainder
bne + bne +
dec P8ZP_SCRATCH_W2+1 dec P8ZP_SCRATCH_W2+1
+ dec P8ZP_SCRATCH_W2 + dec P8ZP_SCRATCH_W2
- lda (P8ZP_SCRATCH_W1), y
sta (P8ZP_SCRATCH_W2), y - lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey dey
bne - bne -
rts rts
_longcopy _longcopy
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
tya tya
tax ; x = num pages (1+) tax ; x = num pages (1+)
ldy #0 ldy #0
- lda (P8ZP_SCRATCH_W1),y ; copy a page at a time - lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y sta (P8ZP_SCRATCH_W2),y
iny iny
bne - bne -
@ -541,7 +546,8 @@ _longcopy
dex dex
bne - bne -
ldy P8ZP_SCRATCH_B1 ldy P8ZP_SCRATCH_B1
jmp _remainder bne _copyshort
rts
}} }}
} }

View File

@ -19,12 +19,27 @@ sub clear_screen() {
txt.chrout(147) txt.chrout(147)
} }
sub home() {
txt.chrout(19)
}
sub nl() { sub nl() {
txt.chrout('\n') txt.chrout('\n')
} }
sub home() { sub spc() {
txt.chrout(19) txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
sec
jsr c64.PLOT
tay
clc
jmp c64.PLOT
}}
} }
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) { asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
@ -571,7 +586,7 @@ _colormod sta $ffff ; modified
} }
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) { asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register. ; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{ %asm {{
stx P8ZP_SCRATCH_REG stx P8ZP_SCRATCH_REG
tax tax

View File

@ -257,7 +257,7 @@ asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1 sty P8ZP_SCRATCH_W1+1
ldy #0 ldy #0
lda (P8ZP_SCRATCH_W1) lda (P8ZP_SCRATCH_W1),y
ldy P8ZP_SCRATCH_W1+1 ldy P8ZP_SCRATCH_W1+1
cmp #'$' cmp #'$'
beq _hex beq _hex

View File

@ -1,33 +1,42 @@
%target cx16 %target cx16
; Bitmap pixel graphics module for the CommanderX16 ; Bitmap pixel graphics routines for the CommanderX16
; Custom routines to use the full-screen 640x480 and 320x240 screen modes. ; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
; This only works on the Cx16. No text layer is currently shown, text can be drawn as part of the bitmap itself. ; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
; Note: for compatible graphics code that words on C64 too, use the "graphics" module instead. ;
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module. ; No text layer is currently shown, text can be drawn as part of the bitmap itself.
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself.
; Note: this library implements code for various resolutions and color depths. This takes up memory.
; If you're memory constrained you should probably not use this built-in library,
; but make a copy in your project only containing the code for the required resolution.
;
;
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 monochrome
; mode 2 = bitmap 320 x 240 x 4c (TODO not yet implemented)
; mode 3 = bitmap 320 x 240 x 16c (TODO not yet implemented)
; mode 4 = bitmap 320 x 240 x 256c
; mode 5 = bitmap 640 x 480 monochrome
; mode 6 = bitmap 640 x 480 x 4c
; higher color dephts in highres are not supported due to lack of VRAM
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver? ; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
gfx2 { gfx2 {
; read-only control variables: ; read-only control variables:
ubyte active_mode = 255 ubyte active_mode = 0
uword width = 0 uword width = 0
uword height = 0 uword height = 0
ubyte bpp = 0 ubyte bpp = 0
ubyte monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes ubyte monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
sub screen_mode(ubyte mode) { sub screen_mode(ubyte mode) {
; mode 0 = bitmap 320 x 240 x 1c monochrome
; mode 1 = bitmap 320 x 240 x 256c
; mode 128 = bitmap 640 x 480 x 1c monochrome
; ...other modes?
; copy the lower-case charset to the upper part of the vram, so we can use it later to plot text
when mode { when mode {
0 -> { 1 -> {
; 320 x 240 x 1c ; lores monchrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1 cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64 cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64 cx16.VERA_DC_VSCALE = 64
@ -38,8 +47,9 @@ gfx2 {
height = 240 height = 240
bpp = 1 bpp = 1
} }
1 -> { ; TODO modes 2, 3 not yet implemented
; 320 x 240 x 256c 4 -> {
; lores 256c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1 cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64 cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64 cx16.VERA_DC_VSCALE = 64
@ -50,8 +60,8 @@ gfx2 {
height = 240 height = 240
bpp = 8 bpp = 8
} }
128 -> { 5 -> {
; 640 x 480 x 1c ; highres monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1 cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128 cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128 cx16.VERA_DC_VSCALE = 128
@ -62,13 +72,26 @@ gfx2 {
height = 480 height = 480
bpp = 1 bpp = 1
} }
255 -> { 6 -> {
; highres 4c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000101
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
bpp = 2
}
else -> {
; back to default text mode and colors ; back to default text mode and colors
cx16.VERA_CTRL = %10000000 ; reset VERA and palette cx16.VERA_CTRL = %10000000 ; reset VERA and palette
c64.CINT() ; back to text mode c64.CINT() ; back to text mode
width = 0 width = 0
height = 0 height = 0
bpp = 0 bpp = 0
mode = 0
} }
} }
@ -81,21 +104,28 @@ gfx2 {
monochrome_stipple(false) monochrome_stipple(false)
position(0, 0) position(0, 0)
when active_mode { when active_mode {
0 -> { 1 -> {
; 320 x 240 x 1c ; lores monochrome
repeat 240/2/8 repeat 240/2/8
cs_innerloop640() cs_innerloop640()
} }
1 -> { ; TODO mode 2, 3
; 320 x 240 x 256c 4 -> {
; lores 256c
repeat 240/2 repeat 240/2
cs_innerloop640() cs_innerloop640()
} }
128 -> { 5 -> {
; 640 x 480 x 1c ; highres monochrome
repeat 480/8 repeat 480/8
cs_innerloop640() cs_innerloop640()
} }
6 -> {
; highres 4c
repeat 480/4
cs_innerloop640()
}
; modes 7 and 8 not supported due to lack of VRAM
} }
position(0, 0) position(0, 0)
} }
@ -130,35 +160,13 @@ gfx2 {
if length==0 if length==0
return return
when active_mode { when active_mode {
1 -> { 1, 5 -> {
; 8bpp mode ; monochrome modes, either resolution
position(x, y)
%asm {{
lda color
phx
ldx length+1
beq +
ldy #0
- sta cx16.VERA_DATA0
iny
bne -
dex
bne -
+ ldy length ; remaining
beq +
- sta cx16.VERA_DATA0
dey
bne -
+ plx
}}
}
0, 128 -> {
; 1 bpp mode
ubyte separate_pixels = (8-lsb(x)) & 7 ubyte separate_pixels = (8-lsb(x)) & 7
if separate_pixels as uword > length if separate_pixels as uword > length
separate_pixels = lsb(length) separate_pixels = lsb(length)
repeat separate_pixels { repeat separate_pixels {
; this could be optimized by setting this byte in 1 go but probably not worth it due to code size ; TODO optimize this by writing a masked byte in 1 go
plot(x, y, color) plot(x, y, color)
x++ x++
} }
@ -200,37 +208,99 @@ _loop lda length
_done _done
}} }}
repeat separate_pixels { repeat separate_pixels {
; this could be optimized by setting this byte in 1 go but probably not worth it due to code size ; TODO optimize this by writing a masked byte in 1 go
plot(x, y, color) plot(x, y, color)
x++ x++
} }
} }
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) ; vera auto-increment off again cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
}
4 -> {
; lores 256c
position(x, y)
%asm {{
lda color
phx
ldx length+1
beq +
ldy #0
- sta cx16.VERA_DATA0
iny
bne -
dex
bne -
+ ldy length ; remaining
beq +
- sta cx16.VERA_DATA0
dey
bne -
+ plx
}}
}
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
color &= 3
ubyte[4] colorbits
ubyte ii
for ii in 3 downto 0 {
colorbits[ii] = color
color <<= 2
}
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
lda cx16.VERA_ADDR_H
and #%00000111 ; no auto advance
sta cx16.VERA_ADDR_H
stz cx16.VERA_CTRL ; setup vera addr 0
lda cx16.r1
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
phx
ldx x
}}
repeat length {
%asm {{
txa
and #3
tay
lda cx16.VERA_DATA0
and gfx2.plot.mask4c,y
ora colorbits,y
sta cx16.VERA_DATA0
cpy #%00000011 ; next vera byte?
bne +
inc cx16.VERA_ADDR_L
bne +
inc cx16.VERA_ADDR_M
+ bne +
inc cx16.VERA_ADDR_H
+ inx ; next pixel
}}
}
%asm {{
plx
}}
} }
} }
} }
sub vertical_line(uword x, uword y, uword height, ubyte color) { sub vertical_line(uword x, uword y, uword height, ubyte color) {
position(x,y) position(x,y)
if active_mode==1 { when active_mode {
; set vera auto-increment to 320 pixel increment (=next line) 1, 5 -> {
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) | (14<<4) ; monochrome, either resolution
%asm {{
ldy height
beq +
lda color
- sta cx16.VERA_DATA0
dey
bne -
+
}}
return
}
; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place. ; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) ; no auto advance ; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if active_mode>=128 if active_mode>=5
cx16.r14 = 640/8 cx16.r14 = 640/8
else else
cx16.r14 = 320/8 cx16.r14 = 320/8
@ -255,7 +325,7 @@ _done
} }
} else { } else {
; stippling. ; stippling.
height = (height+1)/2 height = (height+1)/2 ; TODO is the line sometimes 1 pixel too long now because of rounding?
%asm {{ %asm {{
lda x lda x
eor y eor y
@ -268,11 +338,11 @@ _done
lda cx16.VERA_ADDR_M lda cx16.VERA_ADDR_M
adc #0 adc #0
sta cx16.VERA_ADDR_M sta cx16.VERA_ADDR_M
+ +
asl cx16.r14 asl cx16.r14
ldy height ldy height
beq + beq +
- lda cx16.VERA_DATA0 - lda cx16.VERA_DATA0
ora cx16.r15 ora cx16.r15
sta cx16.VERA_DATA0 sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L lda cx16.VERA_ADDR_L
@ -287,7 +357,7 @@ _done
; sta cx16.VERA_ADDR_H ; sta cx16.VERA_ADDR_H
dey dey
bne - bne -
+ +
}} }}
} }
} else { } else {
@ -304,17 +374,66 @@ _done
lda cx16.VERA_ADDR_M lda cx16.VERA_ADDR_M
adc #0 adc #0
sta cx16.VERA_ADDR_M sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part. ; the bitmap size is small enough to not have to deal with the _H part:
; lda cx16.VERA_ADDR_H
; adc #0 ; adc #0
; sta cx16.VERA_ADDR_H ; sta cx16.VERA_ADDR_H
}} }}
} }
} }
} }
4 -> {
; lores 256c
; set vera auto-increment to 320 pixel increment (=next line)
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
%asm {{
ldy height
beq +
lda color
- sta cx16.VERA_DATA0
dey
bne -
+
}}
}
6 -> {
; highres 4c
; note for this mode we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
; TODO optimize the loop in pure assembly
color &= 3
color <<= gfx2.plot.shift4c[lsb(x) & 3]
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
repeat height {
ubyte value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask | color
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
%asm {{
; 24 bits add 160 (640/4)
clc
lda cx16.r0
adc #640/4
sta cx16.r0
lda cx16.r0+1
adc #0
sta cx16.r0+1
bcc +
inc cx16.r1
+
}}
}
}
}
}
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) { sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
; Bresenham algorithm. ; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations. ; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; TODO there are some slight errors at the first/last pixels in certain slopes...
if y1>y2 { if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases ; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2) swap(x1, x2)
@ -468,11 +587,14 @@ _done
sub plot(uword @zp x, uword y, ubyte color) { sub plot(uword @zp x, uword y, ubyte color) {
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1] ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100]
ubyte[4] shift4c = [6,4,2,0]
uword addr uword addr
ubyte value ubyte value
when active_mode { when active_mode {
0 -> { 1 -> {
; lores monochrome
%asm {{ %asm {{
lda x lda x
eor y eor y
@ -490,7 +612,16 @@ _done
} }
} }
} }
128 -> { 4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.vpoke(lsb(cx16.r1), cx16.r0, color)
; activate vera auto-increment mode so next_pixel() can be used after this
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | %00010000
color = cx16.VERA_DATA0
}
5 -> {
; highres monochrome
%asm {{ %asm {{
lda x lda x
eor y eor y
@ -508,30 +639,44 @@ _done
} }
} }
} }
1 -> { 6 -> {
void addr_mul_320_add_24(y, x) ; 24 bits result is in r0 and r1L ; highres 4c
value = lsb(cx16.r1) ; TODO also mostly usable for lores 4c?
cx16.vpoke(value, cx16.r0, color) void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
; activate vera auto-increment mode so next_pixel() can be used after this color &= 3
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) | %00010000 color <<= shift4c[lsb(x) & 3]
color = cx16.VERA_DATA0 ; TODO optimize the vera memory manipulation in pure assembly
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask4c[lsb(x) & 3] | color
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
} }
} }
} }
sub position(uword @zp x, uword y) { sub position(uword @zp x, uword y) {
ubyte bank
when active_mode { when active_mode {
0 -> { 1 -> {
; lores monochrome
cx16.r0 = y*(320/8) + x/8 cx16.r0 = y*(320/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1) cx16.vaddr(0, cx16.r0, 0, 1)
} }
128 -> { ; TODO modes 2,3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 0, 1)
}
5 -> {
; highres monochrome
cx16.r0 = y*(640/8) + x/8 cx16.r0 = y*(640/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1) cx16.vaddr(0, cx16.r0, 0, 1)
} }
1 -> { 6 -> {
void addr_mul_320_add_24(y, x) ; 24 bits result is in r0 and r1L ; highres 4c
ubyte bank = lsb(cx16.r1) void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 0, 1) cx16.vaddr(bank, cx16.r0, 0, 1)
} }
} }
@ -541,6 +686,7 @@ _done
; -- sets the next pixel byte to the graphics chip. ; -- sets the next pixel byte to the graphics chip.
; for 8 bpp screens this will plot 1 pixel. ; for 8 bpp screens this will plot 1 pixel.
; for 1 bpp screens it will plot 8 pixels at once (color = bit pattern). ; for 1 bpp screens it will plot 8 pixels at once (color = bit pattern).
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
%asm {{ %asm {{
sta cx16.VERA_DATA0 sta cx16.VERA_DATA0
}} }}
@ -550,6 +696,7 @@ _done
; -- sets the next bunch of pixels from a prepared array of bytes. ; -- sets the next bunch of pixels from a prepared array of bytes.
; for 8 bpp screens this will plot 1 pixel per byte. ; for 8 bpp screens this will plot 1 pixel per byte.
; for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte). ; for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte).
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
%asm {{ %asm {{
phx phx
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
@ -614,13 +761,13 @@ _done
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) { sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!). ; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use. ; You must also have called text_charset() first to select and prepare the character set to use.
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to mulitples of 8 ! ; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to mulitples of 8 ! TODO allow per-pixel horizontal positioning
uword chardataptr uword chardataptr
when active_mode { when active_mode {
0, 128 -> { 1, 5 -> {
; 1-bitplane modes ; monochrome mode, either resolution
cx16.r2 = 40 cx16.r2 = 40
if active_mode>=128 if active_mode>=5
cx16.r2 = 80 cx16.r2 = 80
while @(sctextptr) { while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8 chardataptr = charset_addr + (@(sctextptr) as uword)*8
@ -660,12 +807,13 @@ _done
sctextptr++ sctextptr++
} }
} }
1 -> { 4 -> {
; 320 x 240 x 256c ; lores 256c
while @(sctextptr) { while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8 chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1) cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 { repeat 8 {
; TODO rewrite this inner loop fully in assembly
position(x,y) position(x,y)
y++ y++
%asm {{ %asm {{
@ -689,6 +837,28 @@ _done
sctextptr++ sctextptr++
} }
} }
6 -> {
; hires 4c
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
repeat 8 {
; TODO rewrite this inner loop fully in assembly
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
repeat 8 {
charbits <<= 1
if_cs
plot(x, y, color)
x++
}
x-=8
chardataptr++
y++
}
x+=8
y-=8
sctextptr++
}
}
} }
} }
@ -709,7 +879,57 @@ _done
}} }}
} }
asmsub addr_mul_320_add_24(uword address @R0, uword value @AY) clobbers(A) -> uword @R0, ubyte @R1 { asmsub addr_mul_24_for_highres_4c(uword yy @R2, uword xx @R3) clobbers(A, Y) -> uword @R0, uword @R1 {
; yy * 160 + xx/4 (24 bits calculation)
; 24 bits result is in r0 and r1L (highest byte)
%asm {{
ldy #5
- asl cx16.r2
rol cx16.r2+1
dey
bne -
lda cx16.r2
sta cx16.r0
lda cx16.r2+1
sta cx16.r0+1
asl cx16.r0
rol cx16.r0+1
asl cx16.r0
rol cx16.r0+1
; xx >>= 2 (xx=R3)
lsr cx16.r3+1
ror cx16.r3
lsr cx16.r3+1
ror cx16.r3
; add r2 and xx (r3) to r0 (24-bits)
stz cx16.r1
clc
lda cx16.r0
adc cx16.r2
sta cx16.r0
lda cx16.r0+1
adc cx16.r2+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ clc
lda cx16.r0
adc cx16.r3
sta cx16.r0
lda cx16.r0+1
adc cx16.r3+1
sta cx16.r0+1
bcc +
inc cx16.r1
+
rts
}}
}
asmsub addr_mul_24_for_lores_256c(uword yy @R0, uword xx @AY) clobbers(A) -> uword @R0, ubyte @R1 {
; yy * 320 + xx (24 bits calculation)
%asm {{ %asm {{
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1 sty P8ZP_SCRATCH_W1+1

View File

@ -9,34 +9,46 @@ palette {
ubyte c ubyte c
sub set_color(ubyte index, uword color) { sub set_color(ubyte index, uword color) {
cx16.vpoke(1, $fa00+index*2, lsb(color)) vera_palette_ptr = $fa00+index*2
cx16.vpoke(1, $fa01+index*2, msb(color)) cx16.vpoke(1, vera_palette_ptr, lsb(color))
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(color))
} }
sub set_rgb4(uword palletteptr, uword num_colors) { sub set_rgb4(uword palette_bytes_ptr, uword num_colors) {
; 2 bytes per color entry, the Vera uses this, but the R/GB bytes order is swapped ; 2 bytes per color entry, the Vera uses this, but the R/GB bytes order is swapped
vera_palette_ptr = $fa00 vera_palette_ptr = $fa00
repeat num_colors { repeat num_colors {
cx16.vpoke(1, vera_palette_ptr+1, @(palletteptr)) cx16.vpoke(1, vera_palette_ptr+1, @(palette_bytes_ptr))
palletteptr++ palette_bytes_ptr++
cx16.vpoke(1, vera_palette_ptr, @(palletteptr)) cx16.vpoke(1, vera_palette_ptr, @(palette_bytes_ptr))
palletteptr++ palette_bytes_ptr++
vera_palette_ptr+=2 vera_palette_ptr+=2
} }
} }
sub set_rgb8(uword palletteptr, uword num_colors) { sub set_rgb(uword palette_words_ptr, uword num_colors) {
; 1 word per color entry (in little endian format so $gb0r)
vera_palette_ptr = $fa00
repeat num_colors*2 {
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
palette_words_ptr++
vera_palette_ptr++
}
}
sub set_rgb8(uword palette_bytes_ptr, uword num_colors) {
; 3 bytes per color entry, adjust color depth from 8 to 4 bits per channel. ; 3 bytes per color entry, adjust color depth from 8 to 4 bits per channel.
vera_palette_ptr = $fa00 vera_palette_ptr = $fa00
ubyte red ubyte red
ubyte greenblue ubyte greenblue
repeat num_colors { repeat num_colors {
red = @(palletteptr) >> 4 red = @(palette_bytes_ptr) >> 4
palletteptr++ palette_bytes_ptr++
greenblue = @(palletteptr) & %11110000 greenblue = @(palette_bytes_ptr) & %11110000
palletteptr++ palette_bytes_ptr++
greenblue |= @(palletteptr) >> 4 ; add Blue greenblue |= @(palette_bytes_ptr) >> 4 ; add Blue
palletteptr++ palette_bytes_ptr++
cx16.vpoke(1, vera_palette_ptr, greenblue) cx16.vpoke(1, vera_palette_ptr, greenblue)
vera_palette_ptr++ vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, red) cx16.vpoke(1, vera_palette_ptr, red)
@ -44,16 +56,16 @@ palette {
} }
} }
sub set_monochrome() { sub set_monochrome(uword screencolorRGB, uword drawcolorRGB) {
vera_palette_ptr = $fa00 vera_palette_ptr = $fa00
cx16.vpoke(1, vera_palette_ptr, 0) cx16.vpoke(1, vera_palette_ptr, lsb(screencolorRGB)) ; G,B
vera_palette_ptr++ vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, 0) cx16.vpoke(1, vera_palette_ptr, msb(screencolorRGB)) ; R
vera_palette_ptr++ vera_palette_ptr++
repeat 255 { repeat 255 {
cx16.vpoke(1, vera_palette_ptr, 255) cx16.vpoke(1, vera_palette_ptr, lsb(drawcolorRGB)) ; G,B
vera_palette_ptr++ vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, 255) cx16.vpoke(1, vera_palette_ptr, msb(drawcolorRGB)) ; R
vera_palette_ptr++ vera_palette_ptr++
} }
} }

View File

@ -102,10 +102,11 @@ asmsub MEMTOP2() -> ubyte @A {
cx16 { cx16 {
; 65c02 hardware vectors: ; irq and hardware vectors:
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in &uword CINV = $0314 ; IRQ vector (in ram)
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in &uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in &uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
; the sixteen virtual 16-bit registers ; the sixteen virtual 16-bit registers
@ -289,6 +290,23 @@ romsub $fecc = monitor() clobbers(A,X,Y)
; ---- utilities ----- ; ---- utilities -----
inline asmsub rombank(ubyte rombank @A) {
; -- set the rom banks
%asm {{
sta $01 ; rom bank register (new)
sta cx16.d1prb ; rom bank register (old)
}}
}
inline asmsub rambank(ubyte rambank @A) {
; -- set the ram bank
%asm {{
sta $00 ; ram bank register (new)
sta cx16.d1pra ; ram bank register (old)
}}
}
asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A { asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
; -- get a byte from VERA's video memory ; -- get a byte from VERA's video memory
; note: inefficient when reading multiple sequential bytes! ; note: inefficient when reading multiple sequential bytes!
@ -444,7 +462,7 @@ asmsub init_system() {
cld cld
;stz $00 ;stz $00
;stz $01 ;stz $01
;stz d1prb ; select rom bank 0 ;stz d1prb ; select rom bank 0 (enable kernal)
lda #$80 lda #$80
sta VERA_CTRL sta VERA_CTRL
jsr c64.IOINIT jsr c64.IOINIT
@ -468,8 +486,177 @@ asmsub init_system() {
}} }}
} }
asmsub init_system_phase2() {
%asm {{
sei
lda cx16.CINV
sta restore_irq._orig_irqvec
lda cx16.CINV+1
sta restore_irq._orig_irqvec+1
cli
rts
}}
} }
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei
lda #<_irq_handler
sta cx16.CINV
lda #>_irq_handler
sta cx16.CINV+1
lda cx16.VERA_IEN
ora #%00000001 ; enable the vsync irq
sta cx16.VERA_IEN
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #1
sta cx16.VERA_ISR ; clear Vera Vsync irq status
ply
plx
pla
rti
+ jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
stx IRQ_X_REG
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
; stack protector; make sure we don't clobber the top of the evaluation stack
dex
dex
dex
dex
dex
dex
cld
rts
_irq_handler_end
; restore all zp scratch registers and the X register
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
ldx IRQ_X_REG
rts
IRQ_X_REG .byte 0
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda _orig_irqvec
sta cx16.CINV
lda _orig_irqvec+1
sta cx16.CINV+1
lda cx16.VERA_IEN
and #%11110000 ; disable all Vera IRQs
ora #%00000001 ; enable only the vsync Irq
sta cx16.VERA_IEN
cli
rts
_orig_irqvec .word 0
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
sei
lda cx16.VERA_IEN
and #%11110000 ; clear other IRQs
ora #%00000010 ; enable the line (raster) irq
sta cx16.VERA_IEN
lda cx16.r0
ldy cx16.r0+1
jsr set_rasterline
lda #<_raster_irq_handler
sta cx16.CINV
lda #>_raster_irq_handler
sta cx16.CINV+1
cli
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #%00000010
sta cx16.VERA_ISR ; clear Vera line irq status
ply
plx
pla
rti
}}
}
asmsub set_rasterline(uword line @AY) {
%asm {{
sta cx16.VERA_IRQ_LINE_L
lda cx16.VERA_IEN
and #%01111111
sta cx16.VERA_IEN
tya
lsr a
ror a
and #%10000000
ora cx16.VERA_IEN
sta cx16.VERA_IEN
rts
}}
}
}
sys { sys {
; ------- lowlevel system routines -------- ; ------- lowlevel system routines --------
@ -480,9 +667,8 @@ sys {
; Soft-reset the system back to Basic prompt. ; Soft-reset the system back to Basic prompt.
%asm {{ %asm {{
sei sei
lda #14 stz $01 ; bank the kernal in (new rom bank register)
sta $01 stz cx16.d1prb ; bank the kernal in (old rom bank register)
stz cx16.d1prb ; bank the kernal in
jmp (cx16.RESET_VEC) jmp (cx16.RESET_VEC)
}} }}
} }

View File

@ -19,12 +19,27 @@ sub clear_screen() {
txt.chrout(147) txt.chrout(147)
} }
sub home() {
txt.chrout(19)
}
sub nl() { sub nl() {
txt.chrout('\n') txt.chrout('\n')
} }
sub home() { sub spc() {
txt.chrout(19) txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
sec
jsr c64.PLOT
tay
clc
jmp c64.PLOT
}}
} }
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) { asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
@ -611,6 +626,8 @@ asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) { asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
; ---- set the color in A on the screen matrix at the given position ; ---- set the color in A on the screen matrix at the given position
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color!
%asm {{ %asm {{
pha pha
txa txa
@ -642,6 +659,8 @@ asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) { sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen ; ---- set char+color at the given position on the screen
; note: color handling is the same as on the C64: it only sets the foreground color.
; use setcc2 if you want Cx-16 specific feature of setting both Bg+Fg colors.
%asm {{ %asm {{
phx phx
lda column lda column
@ -670,8 +689,35 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
}} }}
} }
sub setcc2 (ubyte column, ubyte row, ubyte char, ubyte colors) {
; ---- set char+color at the given position on the screen
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color!
%asm {{
phx
lda column
asl a
tax
ldy row
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda colors
sta cx16.VERA_DATA0
plx
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) { asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register. ; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{ %asm {{
phx phx
tax tax

View File

@ -30,7 +30,7 @@ diskio {
ubyte low = c64.CHRIN() ubyte low = c64.CHRIN()
ubyte high = c64.CHRIN() ubyte high = c64.CHRIN()
txt.print_uw(mkword(high, low)) txt.print_uw(mkword(high, low))
txt.chrout(' ') txt.spc()
ubyte @zp char ubyte @zp char
repeat { repeat {
char = c64.CHRIN() char = c64.CHRIN()
@ -72,7 +72,7 @@ io_error:
str list_filename = "?" * 32 str list_filename = "?" * 32
; ----- get a list of files (uses iteration functions internally ----- ; ----- get a list of files (uses iteration functions internally) -----
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte { sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested. ; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested.
@ -295,7 +295,8 @@ _in_buffer sta $ffff
; Routine to read text lines from a text file. Lines must be less than 255 characters. ; Routine to read text lines from a text file. Lines must be less than 255 characters.
; Reads characters from the input file UNTIL a newline or return character (or EOF). ; Reads characters from the input file UNTIL a newline or return character (or EOF).
; The line read will be 0-terminated in the buffer (and not contain the end of line character). ; The line read will be 0-terminated in the buffer (and not contain the end of line character).
; The length of the line is returned in Y. ; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
; I/O error status should be checked by the caller itself via READST() routine.
%asm {{ %asm {{
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1 sty P8ZP_SCRATCH_W1+1
@ -423,10 +424,9 @@ io_error:
sub delete(ubyte drivenumber, uword filenameptr) { sub delete(ubyte drivenumber, uword filenameptr) {
; -- delete a file on the drive ; -- delete a file on the drive
ubyte flen = string.length(filenameptr)
filename[0] = 's' filename[0] = 's'
filename[1] = ':' filename[1] = ':'
sys.memcopy(filenameptr, &filename+2, flen+1) ubyte flen = string.copy(filenameptr, &filename+2)
c64.SETNAM(flen+2, filename) c64.SETNAM(flen+2, filename)
c64.SETLFS(1, drivenumber, 15) c64.SETLFS(1, drivenumber, 15)
void c64.OPEN() void c64.OPEN()
@ -436,13 +436,11 @@ io_error:
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) { sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
; -- rename a file on the drive ; -- rename a file on the drive
ubyte flen_old = string.length(oldfileptr)
ubyte flen_new = string.length(newfileptr)
filename[0] = 'r' filename[0] = 'r'
filename[1] = ':' filename[1] = ':'
sys.memcopy(newfileptr, &filename+2, flen_new) ubyte flen_new = string.copy(newfileptr, &filename+2)
filename[flen_new+2] = '=' filename[flen_new+2] = '='
sys.memcopy(oldfileptr, &filename+3+flen_new, flen_old+1) ubyte flen_old = string.copy(oldfileptr, &filename+3+flen_new)
c64.SETNAM(3+flen_new+flen_old, filename) c64.SETNAM(3+flen_new+flen_old, filename)
c64.SETLFS(1, drivenumber, 15) c64.SETLFS(1, drivenumber, 15)
void c64.OPEN() void c64.OPEN()

View File

@ -1262,7 +1262,7 @@ mul_word_100 .proc
.pend .pend
mul_word_320 .proc mul_word_320 .proc
; AY = A * 256 + A * 64 (msb doesn't matter) ; AY = A * 256 + A * 64 (msb in Y doesn't matter)
sta P8ZP_SCRATCH_B1 sta P8ZP_SCRATCH_B1
ldy #0 ldy #0
sty P8ZP_SCRATCH_REG sty P8ZP_SCRATCH_REG

View File

@ -1078,3 +1078,28 @@ _loop_hi ldy _index_first
.pend .pend
func_peekw .proc
; -- read the word value on the address in AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
.pend
func_pokew .proc
; -- store the word value in AY in the address in P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_REG
ldy #0
sta (P8ZP_SCRATCH_W1),y
iny
lda P8ZP_SCRATCH_REG
sta (P8ZP_SCRATCH_W1),y
rts
.pend

View File

@ -1071,3 +1071,4 @@ sign_extend_AY_byte .proc
pla pla
rts rts
.pend .pend

View File

@ -1 +1 @@
6.0-BETA 6.2

View File

@ -1,12 +1,14 @@
package prog8 package prog8
import kotlinx.cli.* import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
import kotlinx.cli.multiple
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.compiler.CompilationResult import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.compiler.target.CompilationTarget
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Path import java.nio.file.Path
@ -32,14 +34,14 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
private fun compileMain(args: Array<String>) { private fun compileMain(args: Array<String>) {
val cli = ArgParser("prog8compiler") val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
val startEmulator by cli.option(ArgType.Boolean, shortName="emu", description = "auto-start emulator after successful compilation") val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
val outputDir by cli.option(ArgType.String, shortName = "out", description = "directory for output files instead of current directory").default(".") val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.option(ArgType.Boolean, shortName = "noasm", description="don't create assembly code") val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, shortName = "noopt", description = "don't perform any optimizations") val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val watchMode by cli.option(ArgType.Boolean, shortName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed") val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, shortName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation") val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.option(ArgType.String, shortName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name) val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999) val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try { try {
@ -114,7 +116,7 @@ private fun compileMain(args: Array<String>) {
if (compilationResult.programName.isEmpty()) if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.") println("\nCan't start emulator because no program was assembled.")
else { else {
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName) compilationResult.compTarget.machine.launchEmulator(compilationResult.programName)
} }
} }
} }

View File

@ -1,22 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.statements.Directive
internal class ImportedModuleDirectiveRemover: AstWalker() {
/**
* Most global directives don't apply for imported modules, so remove them
*/
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
private val noModifications = emptyList<IAstModification>()
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent as INameScope))
}
return noModifications
}
}

View File

@ -1,44 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.NopStatement
internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
into.statements.remove(scope)
}
}
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
return noModifications
}
}

View File

@ -5,12 +5,13 @@ import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() { internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
@ -37,7 +38,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF. // But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable if(!assignment.isAugmentable
&& assignment.target.identifier != null && assignment.target.identifier != null
&& assignment.target.isInRegularRAM(program.namespace)) { && compTarget.isInRegularRAM(assignment.target, program)) {
val binExpr = assignment.value as? BinaryExpression val binExpr = assignment.value as? BinaryExpression
if (binExpr != null && binExpr.operator !in comparisonOperators) { if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) { if (binExpr.left !is BinaryExpression) {
@ -106,7 +107,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
} }
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine. // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations. // and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>() val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position) val returnStmt = Return(null, subroutine.position)
@ -156,7 +157,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
if(typecast.type in WordDatatypes) { if(typecast.type in WordDatatypes) {
val fcall = typecast.parent as? IFunctionCall val fcall = typecast.parent as? IFunctionCall
if (fcall != null) { if (fcall != null) {
val sub = fcall.target.targetStatement(program.namespace) as? Subroutine val sub = fcall.target.targetStatement(program) as? Subroutine
if (sub != null && sub.isAsmSubroutine) { if (sub != null && sub.isAsmSubroutine) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent)) return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
} }

View File

@ -1,9 +1,30 @@
package prog8.compiler package prog8.compiler
import prog8.ast.AstToSourceCode
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Directive
import prog8.compiler.astprocessing.*
import prog8.compiler.functions.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.asmGeneratorFor
import prog8.optimizer.*
import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.moduleName
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.nio.file.Path import java.nio.file.Path
import kotlin.math.abs import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
enum class OutputType { enum class OutputType {
RAW, RAW,
@ -28,7 +49,8 @@ data class CompilationOptions(val output: OutputType,
val zeropage: ZeropageType, val zeropage: ZeropageType,
val zpReserved: List<IntRange>, val zpReserved: List<IntRange>,
val floats: Boolean, val floats: Boolean,
val noSysInit: Boolean) { val noSysInit: Boolean,
val compTarget: ICompilationTarget) {
var slowCodegenWarnings = false var slowCodegenWarnings = false
var optimize = false var optimize = false
} }
@ -36,20 +58,268 @@ data class CompilationOptions(val output: OutputType,
class CompilerException(message: String?) : Exception(message) class CompilerException(message: String?) : Exception(message)
fun Number.toHex(): String { class CompilationResult(val success: Boolean,
// 0..15 -> "0".."15" val programAst: Program,
// 16..255 -> "$10".."$ff" val programName: String,
// 256..65536 -> "$0100".."$ffff" val compTarget: ICompilationTarget,
// negative values are prefixed with '-'. val importedFiles: List<Path>)
val integer = this.toInt()
if(integer<0)
return '-' + abs(integer).toHex() fun compileProgram(filepath: Path,
return when (integer) { optimize: Boolean,
in 0 until 16 -> integer.toString() writeAssembly: Boolean,
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0') slowCodegenWarnings: Boolean,
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0') compilationTarget: String,
else -> throw CompilerException("number too large for 16 bits $this") outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
val compTarget =
when(compilationTarget) {
C64Target.name -> C64Target
Cx16Target.name -> Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
} }
}
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
print("\u001b[0m") // reset
System.out.flush()
throw x
} catch (x: NotImplementedError) {
print("\u001b[91m") // bright red
println("\n* internal error: missing feature/code *")
print("\u001b[0m") // reset
System.out.flush()
throw x
}
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
}
private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions {
lateinit var program: Program
override val names = functions.keys
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? {
val func = BuiltinFunctions[name]
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null) {
return try {
exprfunc(args, position, program, memsizer)
} catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed.
null
} catch(x: CannotEvaluateException) {
// const-evaluating the builtin function call failed.
null
}
}
else if(func.known_returntype==null)
throw IllegalArgumentException("builtin function $name can't be used here because it doesn't return a value")
}
return null
}
override fun returnType(name: String, args: MutableList<Expression>) =
builtinFunctionReturnType(name, args, program)
}
private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget): Triple<Program, CompilationOptions, List<Path>> {
val compilationTargetName = compTarget.name
println("Compiler target: $compilationTargetName. Parsing...")
val importer = ModuleImporter()
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
bf.program = programAst
importer.importModule(programAst, filepath, compTarget, compilationTargetName)
errors.report()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst, compTarget)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName))
importer.importLibraryModule(programAst, lib, compTarget, compilationTargetName)
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math", compTarget, compilationTargetName)
importer.importLibraryModule(programAst, "prog8_lib", compTarget, compilationTargetName)
errors.report()
return Triple(programAst, compilerOptions, importedFiles)
}
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
val mainModule = program.mainModule
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
System.err.println("invalid output type $outputType")
exitProcess(1)
}
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, noSysInit,
compTarget
)
}
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
errors.report()
programAst.constantFold(errors, compilerOptions.compTarget)
errors.report()
programAst.reorderStatements(errors)
errors.report()
programAst.addTypecasts(errors)
errors.report()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
errors.report()
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
errors.report()
}
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget, ::loadAsmIncludeFile)
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
errors.report()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
remover.visit(programAst)
remover.applyModifications()
errors.report()
}
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
errors.report()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
errors.report()
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
callGraph.checkRecursiveCalls(errors)
errors.report()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private fun writeAssembly(programAst: Program,
errors: IErrorReporter,
outputDir: Path,
compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
errors.report()
// printAst(programAst)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget,
programAst,
errors,
compilerOptions.compTarget.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
assembly.assemble(compilerOptions)
errors.report()
return assembly.name
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
} }
fun loadAsmIncludeFile(filename: String, source: Path): String { fun loadAsmIncludeFile(filename: String, source: Path): String {

View File

@ -1,9 +1,18 @@
package prog8.ast.base package prog8.compiler
import prog8.ast.base.Position
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
class ErrorReporter { interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun isEmpty(): Boolean
fun report()
}
internal class ErrorReporter: IErrorReporter {
private enum class MessageSeverity { private enum class MessageSeverity {
WARNING, WARNING,
ERROR ERROR
@ -13,10 +22,14 @@ class ErrorReporter {
private val messages = mutableListOf<CompilerMessage>() private val messages = mutableListOf<CompilerMessage>()
private val alreadyReportedMessages = mutableSetOf<String>() private val alreadyReportedMessages = mutableSetOf<String>()
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position)) override fun err(msg: String, position: Position) {
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position)) messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
}
override fun warn(msg: String, position: Position) {
messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
}
fun handle() { override fun report() {
var numErrors = 0 var numErrors = 0
var numWarnings = 0 var numWarnings = 0
messages.forEach { messages.forEach {
@ -40,5 +53,5 @@ class ErrorReporter {
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.") throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
} }
fun isEmpty() = messages.isEmpty() override fun isEmpty() = messages.isEmpty()
} }

View File

@ -1,247 +0,0 @@
package prog8.compiler
import prog8.ast.AstToSourceCode
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.statements.Directive
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.optimizer.*
import prog8.optimizer.UnusedCodeRemover
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.moduleName
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
class CompilationResult(val success: Boolean,
val programAst: Program,
val programName: String,
val importedFiles: List<Path>)
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
compilationTarget: String,
outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
when(compilationTarget) {
C64Target.name -> CompilationTarget.instance = C64Target
Cx16Target.name -> CompilationTarget.instance = Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, importedFiles)
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
print("\u001b[0m") // reset
System.out.flush()
throw x
} catch (x: NotImplementedError) {
print("\u001b[91m") // bright red
println("\n* internal error: missing feature/code *")
print("\u001b[0m") // reset
System.out.flush()
throw x
}
return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList())
}
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
println("Compiler target: ${CompilationTarget.instance.name}. Parsing...")
val importer = ModuleImporter()
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
importer.importModule(programAst, filepath)
errors.handle()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// depending on the machine and compiler options we may have to include some libraries
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math")
importer.importLibraryModule(programAst, "prog8_lib")
errors.handle()
return Triple(programAst, compilerOptions, importedFiles)
}
private fun determineCompilationOptions(program: Program): CompilationOptions {
val mainModule = program.modules.first()
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && CompilationTarget.instance.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
System.err.println("invalid output type $outputType")
exitProcess(1)
}
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, noSysInit
)
}
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${CompilationTarget.instance.name}...")
programAst.checkIdentifiers(errors)
errors.handle()
programAst.constantFold(errors)
errors.handle()
programAst.reorderStatements(errors)
errors.handle()
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors)
errors.handle()
programAst.checkIdentifiers(errors)
errors.handle()
}
private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions()
val optsDone3 = programAst.optimizeStatements(errors)
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
errors.handle()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
val remover = UnusedCodeRemover(programAst, errors)
remover.visit(programAst)
remover.applyModifications()
errors.handle()
}
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
errors.handle()
val callGraph = CallGraph(programAst)
callGraph.checkRecursiveCalls(errors)
errors.handle()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
programAst.processAstBeforeAsmGeneration(errors)
errors.handle()
// printAst(programAst)
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
val assembly = CompilationTarget.instance.asmGenerator(
programAst,
errors,
CompilationTarget.instance.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
assembly.assemble(compilerOptions)
errors.handle()
return assembly.name
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}

View File

@ -21,7 +21,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int { fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"} assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE) if(options.zeropage==ZeropageType.DONTUSE)

View File

@ -1,4 +1,4 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Module import prog8.ast.Module
@ -6,17 +6,21 @@ import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.functions.BuiltinFunctions import prog8.compiler.target.ICompilationTarget
import prog8.functions.builtinFunctionReturnType
import java.io.File import java.io.File
internal class AstChecker(private val program: Program, internal class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions, private val compilerOptions: CompilationOptions,
private val errors: ErrorReporter) : IAstVisitor { private val errors: IErrorReporter,
private val compTarget: ICompilationTarget
) : IAstVisitor {
override fun visit(program: Program) { override fun visit(program: Program) {
assert(program === this.program) assert(program === this.program)
@ -37,19 +41,6 @@ internal class AstChecker(private val program: Program,
} }
} }
// there can be an optional single 'irq' block with a 'irq' subroutine in it,
// which will be used as the 60hz irq routine in the vm if it's present
val irqBlocks = program.modules.flatMap { it.statements }.filter { it is Block && it.name=="irq" }.map { it as Block }
if(irqBlocks.size>1)
errors.err("more than one 'irq' block", irqBlocks[0].position)
for(irqBlock in irqBlocks) {
val irqSub = irqBlock.subScope("irq") as? Subroutine
if (irqSub != null) {
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
}
}
super.visit(program) super.visit(program)
} }
@ -99,7 +90,7 @@ internal class AstChecker(private val program: Program,
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) { if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
errors.err("can only loop over an iterable type", forLoop.position) errors.err("can only loop over an iterable type", forLoop.position)
} else { } else {
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace) val loopvar = forLoop.loopVar.targetVarDecl(program)
if(loopvar==null || loopvar.type== VarDeclType.CONST) { if(loopvar==null || loopvar.type== VarDeclType.CONST) {
errors.err("for loop requires a variable to loop with", forLoop.position) errors.err("for loop requires a variable to loop with", forLoop.position)
} else { } else {
@ -150,21 +141,24 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(jump: Jump) { override fun visit(jump: Jump) {
if(jump.identifier!=null) { val ident = jump.identifier
val targetStatement = checkFunctionOrLabelExists(jump.identifier, jump) if(ident!=null) {
val targetStatement = checkFunctionOrLabelExists(ident, jump)
if(targetStatement!=null) { if(targetStatement!=null) {
if(targetStatement is BuiltinFunctionStatementPlaceholder) if(targetStatement is BuiltinFunctionStatementPlaceholder)
errors.err("can't jump to a builtin function", jump.position) errors.err("can't jump to a builtin function", jump.position)
} }
} }
if(jump.address!=null && (jump.address < 0 || jump.address > 65535)) val addr = jump.address
if(addr!=null && (addr < 0 || addr > 65535))
errors.err("jump address must be valid integer 0..\$ffff", jump.position) errors.err("jump address must be valid integer 0..\$ffff", jump.position)
super.visit(jump) super.visit(jump)
} }
override fun visit(block: Block) { override fun visit(block: Block) {
if(block.address!=null && (block.address<0 || block.address>65535)) { val addr = block.address
if(addr!=null && (addr<0 || addr>65535)) {
errors.err("block memory address must be valid integer 0..\$ffff", block.position) errors.err("block memory address must be valid integer 0..\$ffff", block.position)
} }
@ -180,7 +174,7 @@ internal class AstChecker(private val program: Program,
else -> false else -> false
} }
if (!ok) { if (!ok) {
errors.err("statement occurs in a block, where it will never be executed. Use it in a subroutine instead.", statement.position) errors.err("non-declarative statement occurs in block scope, where it will never be executed. Move it to a subroutine instead.", statement.position)
break break
} }
} }
@ -315,9 +309,11 @@ internal class AstChecker(private val program: Program,
RegisterOrPair.R13, RegisterOrPair.R13,
RegisterOrPair.R14, RegisterOrPair.R14,
RegisterOrPair.R15 -> { /* no sensible way to count this */ } RegisterOrPair.R15 -> { /* no sensible way to count this */ }
null -> null -> {
if(p.statusflag!=null) val statusf = p.statusflag
statusflagCounts[p.statusflag] = statusflagCounts.getValue(p.statusflag) + 1 if (statusf != null)
statusflagCounts[statusf] = statusflagCounts.getValue(statusf) + 1
}
} }
} }
} }
@ -342,10 +338,6 @@ internal class AstChecker(private val program: Program,
if(statusFlagsNoCarry.isNotEmpty()) if(statusFlagsNoCarry.isNotEmpty())
err("can only use Carry as status flag parameter") err("can only use Carry as status flag parameter")
val carryParameter = subroutine.asmParameterRegisters.singleOrNull { it.statusflag==Statusflag.Pc }
if(carryParameter!=null && carryParameter !== subroutine.asmParameterRegisters.last())
err("carry parameter has to come last")
} else { } else {
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly // Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD). // Instead, their reference (address) should be passed (as an UWORD).
@ -376,7 +368,7 @@ internal class AstChecker(private val program: Program,
override fun visit(assignment: Assignment) { override fun visit(assignment: Assignment) {
if(assignment.value is FunctionCall) { if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace) val stmt = (assignment.value as FunctionCall).target.targetStatement(program)
if (stmt is Subroutine) { if (stmt is Subroutine) {
val idt = assignment.target.inferType(program) val idt = assignment.target.inferType(program)
if(!idt.isKnown) { if(!idt.isKnown) {
@ -390,7 +382,7 @@ internal class AstChecker(private val program: Program,
val targetIdent = assignment.target.identifier val targetIdent = assignment.target.identifier
if(targetIdent!=null) { if(targetIdent!=null) {
val targetVar = targetIdent.targetVarDecl(program.namespace) val targetVar = targetIdent.targetVarDecl(program)
if(targetVar?.struct != null) { if(targetVar?.struct != null) {
val sourceStructLv = assignment.value as? ArrayLiteralValue val sourceStructLv = assignment.value as? ArrayLiteralValue
if (sourceStructLv != null) { if (sourceStructLv != null) {
@ -399,7 +391,7 @@ internal class AstChecker(private val program: Program,
} else { } else {
val sourceIdent = assignment.value as? IdentifierReference val sourceIdent = assignment.value as? IdentifierReference
if (sourceIdent != null) { if (sourceIdent != null) {
val sourceVar = sourceIdent.targetVarDecl(program.namespace) val sourceVar = sourceIdent.targetVarDecl(program)
if (sourceVar?.struct != null) { if (sourceVar?.struct != null) {
if (sourceVar.struct !== targetVar.struct) if (sourceVar.struct !== targetVar.struct)
errors.err("assignment of different struct types", assignment.position) errors.err("assignment of different struct types", assignment.position)
@ -487,7 +479,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(addressOf: AddressOf) { override fun visit(addressOf: AddressOf) {
val variable=addressOf.identifier.targetVarDecl(program.namespace) val variable=addressOf.identifier.targetVarDecl(program)
if(variable!=null if(variable!=null
&& variable.datatype !in ArrayDatatypes && variable.datatype !in ArrayDatatypes
&& variable.type!=VarDeclType.MEMORY && variable.type!=VarDeclType.MEMORY
@ -747,7 +739,7 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level") err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty()) if(directive.args.isEmpty())
err("missing option directive argument(s)") err("missing option directive argument(s)")
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit")}.any { !it }) else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
err("invalid option directive argument(s)") err("invalid option directive argument(s)")
} }
"%target" -> { "%target" -> {
@ -782,7 +774,7 @@ internal class AstChecker(private val program: Program,
fun isPassByReferenceElement(e: Expression): Boolean { fun isPassByReferenceElement(e: Expression): Boolean {
if(e is IdentifierReference) { if(e is IdentifierReference) {
val decl = e.targetVarDecl(program.namespace)!! val decl = e.targetVarDecl(program)!!
return decl.datatype in PassByReferenceDatatypes return decl.datatype in PassByReferenceDatatypes
} }
return e is StringLiteralValue return e is StringLiteralValue
@ -790,7 +782,7 @@ internal class AstChecker(private val program: Program,
if(array.parent is VarDecl) { if(array.parent is VarDecl) {
if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) }) if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
errors.err("array literal for variable initialization contains invalid types", array.position) errors.err("array literal for variable initialization contains non-constant elements", array.position)
} else if(array.parent is ForLoop) { } else if(array.parent is ForLoop) {
if (!array.value.all { it.constValue(program) != null }) if (!array.value.all { it.constValue(program) != null })
errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position) errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position)
@ -935,12 +927,12 @@ internal class AstChecker(private val program: Program,
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position) errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
} }
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program) val error = VerifyFunctionArgTypes.checkTypes(functionCall, program)
if(error!=null) if(error!=null)
errors.err(error, functionCall.position) errors.err(error, functionCall.position)
// check the functions that return multiple returnvalues. // check the functions that return multiple returnvalues.
val stmt = functionCall.target.targetStatement(program.namespace) val stmt = functionCall.target.targetStatement(program)
if (stmt is Subroutine) { if (stmt is Subroutine) {
if (stmt.returntypes.size > 1) { if (stmt.returntypes.size > 1) {
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine. // Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
@ -998,7 +990,8 @@ internal class AstChecker(private val program: Program,
} }
} }
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, functionCallStatement.definingScope(), program) val error =
VerifyFunctionArgTypes.checkTypes(functionCallStatement, program)
if(error!=null) { if(error!=null) {
errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position) errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position)
} }
@ -1025,7 +1018,7 @@ internal class AstChecker(private val program: Program,
errors.err("swap requires args of numerical type", position) errors.err("swap requires args of numerical type", position)
} }
else if(target.name=="all" || target.name=="any") { else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) { if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program)?.datatype == DataType.STR) {
errors.err("any/all on a string is useless (is always true unless the string is empty)", position) errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
} }
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) { if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
@ -1039,6 +1032,28 @@ internal class AstChecker(private val program: Program,
if (!argIDt.isKnown) if (!argIDt.isKnown)
return return
} }
// check that cx16 virtual registers aren't used as arguments in a conflicting way
val params = target.asmParameterRegisters.withIndex().toList()
for(arg in args.withIndex()) {
var ident: IdentifierReference? = null
if(arg.value is IdentifierReference)
ident = arg.value as IdentifierReference
else if(arg.value is FunctionCall) {
val fcall = arg.value as FunctionCall
if(fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))
ident = fcall.args[0] as? IdentifierReference
}
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].toUpperCase())
val same = params.filter { it.value.registerOrPair==reg }
for(s in same) {
if(s.index!=arg.index) {
errors.err("conflicting register $reg used as argument but is also a target register for another parameter", ident.position)
}
}
}
}
} }
} }
} }
@ -1058,7 +1073,7 @@ internal class AstChecker(private val program: Program,
} }
} }
} else if(postIncrDecr.target.arrayindexed != null) { } else if(postIncrDecr.target.arrayindexed != null) {
val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program.namespace) val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program)
if(target==null) { if(target==null) {
errors.err("undefined symbol", postIncrDecr.position) errors.err("undefined symbol", postIncrDecr.position)
} }
@ -1073,7 +1088,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) { override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
val target = arrayIndexedExpression.arrayvar.targetStatement(program.namespace) val target = arrayIndexedExpression.arrayvar.targetStatement(program)
if(target is VarDecl) { if(target is VarDecl) {
if(target.datatype !in IterableDatatypes) if(target.datatype !in IterableDatatypes)
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position) errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
@ -1113,12 +1128,18 @@ internal class AstChecker(private val program: Program,
val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT) val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT)
if(conditionType !in IntegerDatatypes) if(conditionType !in IntegerDatatypes)
errors.err("when condition must be an integer value", whenStatement.position) errors.err("when condition must be an integer value", whenStatement.position)
val choiceValues = whenStatement.choiceValues(program) val tally = mutableSetOf<Int>()
val occurringValues = choiceValues.map {it.first} for((choices, choiceNode) in whenStatement.choiceValues(program)) {
val tally = choiceValues.associate { it.second to occurringValues.count { ov->it.first==ov} } if(choices!=null) {
tally.filter { it.value>1 }.forEach { for (c in choices) {
errors.err("choice value occurs multiple times", it.key.position) if(c in tally)
errors.err("choice value already occurs earlier", choiceNode.position)
else
tally.add(c)
} }
}
}
if(whenStatement.choices.isEmpty()) if(whenStatement.choices.isEmpty())
errors.err("empty when statement", whenStatement.position) errors.err("empty when statement", whenStatement.position)
@ -1165,7 +1186,7 @@ internal class AstChecker(private val program: Program,
} }
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? { private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
val targetStatement = target.targetStatement(program.namespace) val targetStatement = target.targetStatement(program)
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder) if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
return targetStatement return targetStatement
else if(targetStatement==null) else if(targetStatement==null)
@ -1255,7 +1276,7 @@ internal class AstChecker(private val program: Program,
// check if the floating point values are all within range // check if the floating point values are all within range
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray() val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
if(doubles.any { it < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || it > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE }) if(doubles.any { it < compTarget.machine.FLOAT_MAX_NEGATIVE || it > compTarget.machine.FLOAT_MAX_POSITIVE })
return err("floating point value overflow") return err("floating point value overflow")
return true return true
} }
@ -1329,7 +1350,7 @@ internal class AstChecker(private val program: Program,
val array = value.value.map { val array = value.value.map {
when (it) { when (it) {
is NumericLiteralValue -> it.number.toInt() is NumericLiteralValue -> it.number.toInt()
is AddressOf -> it.identifier.heapId(program.namespace) is AddressOf -> it.identifier.hashCode() and 0xffff
is TypecastExpression -> { is TypecastExpression -> {
val constVal = it.expression.constValue(program) val constVal = it.expression.constValue(program)
val cast = constVal?.cast(it.type) val cast = constVal?.cast(it.type)
@ -1383,12 +1404,15 @@ internal class AstChecker(private val program: Program,
if(sourceDatatype==DataType.STRUCT) { if(sourceDatatype==DataType.STRUCT) {
val structLv = sourceValue as ArrayLiteralValue val structLv = sourceValue as ArrayLiteralValue
val numValues = structLv.value.size val numValues = structLv.value.size
val targetstruct = target.identifier!!.targetVarDecl(program.namespace)!!.struct!! val targetstruct = target.identifier!!.targetVarDecl(program)!!.struct!!
return targetstruct.numberOfElements == numValues return targetstruct.numberOfElements == numValues
} }
false false
} }
else -> errors.err("cannot assign new value to variable of type $targetDatatype", position) else -> {
errors.err("cannot assign new value to variable of type $targetDatatype", position)
false
}
} }
if(result) if(result)

View File

@ -1,31 +1,32 @@
package prog8.ast.base package prog8.compiler.astprocessing
import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.processing.* import prog8.ast.base.FatalAstException
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.compiler.CompilationOptions
import prog8.compiler.BeforeAsmGenerationAstChanger import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) { internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
val checker = AstChecker(this, compilerOptions, errors) val checker = AstChecker(this, compilerOptions, errors, compTarget)
checker.visit(this) checker.visit(this)
} }
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) { internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
val fixer = BeforeAsmGenerationAstChanger(this, errors) val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
fixer.visit(this) fixer.visit(this)
fixer.applyModifications() fixer.applyModifications()
} }
internal fun Program.reorderStatements(errors: ErrorReporter) { internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors) val reorder = StatementReorderer(this, errors)
reorder.visit(this) reorder.visit(this)
reorder.applyModifications() reorder.applyModifications()
} }
internal fun Program.addTypecasts(errors: ErrorReporter) { internal fun Program.addTypecasts(errors: IErrorReporter) {
val caster = TypecastsAdder(this, errors) val caster = TypecastsAdder(this, errors)
caster.visit(this) caster.visit(this)
caster.applyModifications() caster.applyModifications()
@ -36,15 +37,9 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this) fixer.visit(this)
} }
internal fun Module.checkImportedValid() { internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) {
val imr = ImportedModuleDirectiveRemover()
imr.visit(this, this.parent)
imr.applyModifications()
}
internal fun Program.checkIdentifiers(errors: ErrorReporter) { val checker2 = AstIdentifiersChecker(this, errors, compTarget)
val checker2 = AstIdentifiersChecker(this, errors)
checker2.visit(this) checker2.visit(this)
if(errors.isEmpty()) { if(errors.isEmpty()) {

View File

@ -1,14 +1,20 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.DataType
import prog8.ast.expressions.* import prog8.ast.base.NumericDatatypes
import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget import prog8.ast.walk.IAstVisitor
import prog8.functions.BuiltinFunctions import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.target.ICompilationTarget
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor { internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
private var blocks = mutableMapOf<String, Block>() private var blocks = mutableMapOf<String, Block>()
private fun nameError(name: String, position: Position, existing: Statement) { private fun nameError(name: String, position: Position, existing: Statement) {
@ -22,7 +28,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
} }
override fun visit(block: Block) { override fun visit(block: Block) {
if(block.name in CompilationTarget.instance.machine.opcodeNames) if(block.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position) errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
val existing = blocks[block.name] val existing = blocks[block.name]
@ -44,8 +50,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
override fun visit(directive: Directive) { override fun visit(directive: Directive) {
if(directive.directive=="%target") { if(directive.directive=="%target") {
val compatibleTarget = directive.args.single().name val compatibleTarget = directive.args.single().name
if (compatibleTarget != CompilationTarget.instance.name) if (compatibleTarget != compTarget.name)
errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position) errors.err("module's compilation target ($compatibleTarget) differs from active target (${compTarget.name})", directive.position)
} }
super.visit(directive) super.visit(directive)
@ -57,7 +63,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if(decl.name in BuiltinFunctions) if(decl.name in BuiltinFunctions)
errors.err("builtin function cannot be redefined", decl.position) errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in CompilationTarget.instance.machine.opcodeNames) if(decl.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
if(decl.datatype==DataType.STRUCT) { if(decl.datatype==DataType.STRUCT) {
@ -96,7 +102,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
} }
override fun visit(subroutine: Subroutine) { override fun visit(subroutine: Subroutine) {
if(subroutine.name in CompilationTarget.instance.machine.opcodeNames) { if(subroutine.name in compTarget.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position) errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) { } else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined // the builtin functions can't be redefined
@ -136,7 +142,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
} }
override fun visit(label: Label) { override fun visit(label: Label) {
if(label.name in CompilationTarget.instance.machine.opcodeNames) if(label.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position) errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) { if(label.name in BuiltinFunctions) {

View File

@ -1,10 +1,16 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.DataType
import prog8.ast.expressions.* import prog8.ast.expressions.BinaryExpression
import prog8.ast.statements.* import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.ParameterVarDecl
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class AstVariousTransforms(private val program: Program) : AstWalker() { internal class AstVariousTransforms(private val program: Program) : AstWalker() {
@ -26,7 +32,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
} }
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// For non-kernel subroutines and non-asm parameters: // For non-kernal subroutines and non-asm parameters:
// inject subroutine params as local variables (if they're not there yet). // inject subroutine params as local variables (if they're not there yet).
val symbolsInSub = subroutine.allDefinedSymbols() val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet() val namesInSub = symbolsInSub.map{ it.first }.toSet()

View File

@ -1,10 +1,15 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.DataType
import prog8.ast.expressions.* import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.statements.* import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.VarDecl
import prog8.ast.statements.WhenChoice
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
@ -12,13 +17,10 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> { override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) { if(string.parent !is VarDecl && string.parent !is WhenChoice) {
// replace the literal string by a identifier reference to a new local vardecl // replace the literal string by a identifier reference to the interned string
val vardecl = VarDecl.createAuto(string) val scopedName = program.internString(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position) val identifier = IdentifierReference(scopedName, string.position)
return listOf( return listOf(IAstModification.ReplaceNode(string, identifier, parent))
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope())
)
} }
return noModifications return noModifications
} }

View File

@ -1,4 +1,4 @@
package prog8.ast.processing package prog8.compiler.astprocessing
/* /*

View File

@ -1,13 +1,16 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() { internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
// Reorders the statements in a way the compiler needs. // Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set. // - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last. // - library blocks are put last.
@ -81,6 +84,24 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
} }
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
// rewrite pointervar[index] into @(pointervar+index)
val indexer = arrayIndexedExpression.indexer
val index = (indexer.indexNum ?: indexer.indexVar)!!
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", index, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// we're part of the target of an assignment, we have to actually change the assign target itself
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
}
}
when (val expr2 = arrayIndexedExpression.indexer.origExpression) { when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
is NumericLiteralValue -> { is NumericLiteralValue -> {
arrayIndexedExpression.indexer.indexNum = expr2 arrayIndexedExpression.indexer.indexNum = expr2
@ -122,7 +143,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
} }
is IFunctionCall -> { is IFunctionCall -> {
val argnum = parent.args.indexOf(expr) val argnum = parent.args.indexOf(expr)
when (val callee = parent.target.targetStatement(program.namespace)) { when (val callee = parent.target.targetStatement(program)) {
is Subroutine -> { is Subroutine -> {
val paramType = callee.parameters[argnum].type val paramType = callee.parameters[argnum].type
if(leftDt isAssignableTo paramType) { if(leftDt isAssignableTo paramType) {
@ -146,6 +167,38 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
else -> return noModifications else -> return noModifications
} }
} }
else if(expr.operator in logicalOperators) {
// make sure that logical expressions like "var and other-logical-expression
// is rewritten as "var!=0 and other-logical-expression", to avoid bitwise boolean and
// generating the wrong results later
fun wrapped(expr: Expression): Expression =
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position)
fun isLogicalExpr(expr: Expression?): Boolean {
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
return true
if(expr is PrefixExpression && expr.operator in logicalOperators)
return true
return false
}
return if(isLogicalExpr(expr.left)) {
if(isLogicalExpr(expr.right))
noModifications
else
listOf(IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr))
} else {
if(isLogicalExpr(expr.right))
listOf(IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr))
else {
listOf(
IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr),
IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr)
)
}
}
}
return noModifications return noModifications
} }
@ -295,16 +348,16 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> { private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!! val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!! val targetVar = identifier.targetVarDecl(program)!!
val alv = assign.value as? ArrayLiteralValue val alv = assign.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position) return flattenArrayAssign(targetVar, alv, identifier, assign.position)
} }
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> { private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!! val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!! val targetVar = identifier.targetVarDecl(program)!!
val sourceIdent = assign.value as IdentifierReference val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!! val sourceVar = sourceIdent.targetVarDecl(program)!!
if(!sourceVar.isArray) { if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position) errors.err("value must be an array", sourceIdent.position)
return emptyList() return emptyList()
@ -324,7 +377,8 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
return emptyList() return emptyList()
} }
// TODO use a pointer loop instead of individual assignments // TODO use memcopy instead of individual assignments after certain amount of elements
// TODO what does assigning a struct var use?
return alv.value.mapIndexed { index, value -> return alv.value.mapIndexed { index, value ->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position) val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
Assignment(AssignTarget(null, idx, null, position), value, value.position) Assignment(AssignTarget(null, idx, null, position), value, value.position)
@ -334,7 +388,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> { private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
val identifier = structAssignment.target.identifier!! val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single() val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!! val targetVar = identifier.targetVarDecl(program)!!
val struct = targetVar.struct!! val struct = targetVar.struct!!
val slv = structAssignment.value as? ArrayLiteralValue val slv = structAssignment.value as? ArrayLiteralValue
@ -358,11 +412,11 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
// TODO use memcopy beyond a certain number of elements // TODO use memcopy beyond a certain number of elements
val identifier = structAssignment.target.identifier!! val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single() val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!! val targetVar = identifier.targetVarDecl(program)!!
val struct = targetVar.struct!! val struct = targetVar.struct!!
when (structAssignment.value) { when (structAssignment.value) {
is IdentifierReference -> { is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!! val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program)!!
when { when {
sourceVar.struct!=null -> { sourceVar.struct!=null -> {
// struct memberwise copy // struct memberwise copy

View File

@ -1,16 +1,18 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() { class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
/* /*
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type. * Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
* (this includes function call arguments) * (this includes function call arguments)
@ -106,18 +108,18 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
} }
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope()) return afterFunctionCallArgs(functionCallStatement)
} }
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> { override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCall, functionCall.definingScope()) return afterFunctionCallArgs(functionCall)
} }
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> { private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter's type // see if a typecast is needed to convert the arguments into the required parameter's type
val modifications = mutableListOf<IAstModification>() val modifications = mutableListOf<IAstModification>()
when(val sub = call.target.targetStatement(scope)) { when(val sub = call.target.targetStatement(program)) {
is Subroutine -> { is Subroutine -> {
sub.parameters.zip(call.args).forEachIndexed { index, pair -> sub.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program) val argItype = pair.second.inferType(program)

View File

@ -0,0 +1,73 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.base.Position
import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
into.statements.remove(scope)
}
}
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
return noModifications
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return before(functionCall as IFunctionCall, parent, functionCall.position)
}
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
if(functionCall.target.nameInSource==listOf("peek")) {
// peek(a) is synonymous with @(a)
val memread = DirectMemoryRead(functionCall.args.single(), position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent))
}
if(functionCall.target.nameInSource==listOf("poke")) {
// poke(a, v) is synonymous with @(a) = v
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
val assign = Assignment(tgt, functionCall.args[1], position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
}
return noModifications
}
}

View File

@ -1,26 +1,26 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.expressions.Expression import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilerException import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions import prog8.compiler.functions.BuiltinFunctions
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor { class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall) { override fun visit(functionCall: FunctionCall) {
val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program) val error = checkTypes(functionCall as IFunctionCall, program)
if(error!=null) if(error!=null)
throw CompilerException(error) throw CompilerException(error)
} }
override fun visit(functionCallStatement: FunctionCallStatement) { override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program) val error = checkTypes(functionCallStatement as IFunctionCall, program)
if (error!=null) if (error!=null)
throw CompilerException(error) throw CompilerException(error)
} }
@ -39,13 +39,13 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
return false return false
} }
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? { fun checkTypes(call: IFunctionCall, program: Program): String? {
val argITypes = call.args.map { it.inferType(program) } val argITypes = call.args.map { it.inferType(program) }
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown } val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
if(firstUnknownDt>=0) if(firstUnknownDt>=0)
return "argument ${firstUnknownDt+1} invalid argument type" return "argument ${firstUnknownDt+1} invalid argument type"
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) } val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope) val target = call.target.targetStatement(program)
if (target is Subroutine) { if (target is Subroutine) {
if(call.args.size != target.parameters.size) if(call.args.size != target.parameters.size)
return "invalid number of arguments" return "invalid number of arguments"

View File

@ -1,5 +1,6 @@
package prog8.functions package prog8.compiler.functions
import prog8.ast.IMemSizer
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
@ -12,7 +13,7 @@ import kotlin.math.*
class FParam(val name: String, val possibleDatatypes: Set<DataType>) class FParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue
class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean) class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
@ -87,6 +88,7 @@ class FSignature(val name: String,
} }
} }
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
private val functionSignatures: List<FSignature> = listOf( private val functionSignatures: List<FSignature> = listOf(
// this set of function have no return value and operate in-place: // this set of function have no return value and operate in-place:
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
@ -96,40 +98,45 @@ private val functionSignatures: List<FSignature> = listOf(
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null), FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null), FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
// these few have a return value depending on the argument(s): // these few have a return value depending on the argument(s):
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof), FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
FSignature("offsetof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinOffsetof),
// normal functions follow: // normal functions follow:
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ), FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) }, FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ), FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ), FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ), FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ), FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) }, FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ), FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ), FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ), FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ), FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) }, FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) }, FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) }, FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) }, FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } }, FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) }, FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) }, FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) }, FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) }, FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) }, FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) }, FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) }, FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) }, FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }}, FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}}, FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword), FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
FSignature("peek" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UBYTE),
FSignature("peekw" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD),
FSignature("poke" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UBYTE))), null),
FSignature("pokew" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UWORD))), null),
FSignature("rnd" , false, emptyList(), DataType.UBYTE), FSignature("rnd" , false, emptyList(), DataType.UBYTE),
FSignature("rndw" , false, emptyList(), DataType.UWORD), FSignature("rndw" , false, emptyList(), DataType.UWORD),
FSignature("rndf" , false, emptyList(), DataType.FLOAT), FSignature("rndf" , false, emptyList(), DataType.FLOAT),
@ -265,7 +272,8 @@ private fun collectionArg(args: List<Expression>, position: Position, program: P
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position) return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
} }
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinAbs(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = float or int, result type= isSameAs as argument type // 1 arg, type = float or int, result type= isSameAs as argument type
if(args.size!=1) if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position) throw SyntaxError("abs requires one numeric argument", position)
@ -278,7 +286,29 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
} }
} }
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("offsetof requires one argument", position)
val idref = args[0] as? IdentifierReference
?: throw SyntaxError("offsetof argument should be an identifier", position)
val vardecl = idref.targetVarDecl(program)!!
val struct = vardecl.struct
if (struct == null || vardecl.datatype == DataType.STRUCT)
throw SyntaxError("offsetof can only be used on struct members", position)
val membername = idref.nameInSource.last()
var offset = 0
for(member in struct.statements) {
if((member as VarDecl).name == membername)
return NumericLiteralValue(DataType.UBYTE, offset, position)
offset += memsizer.memorySize(member.datatype)
}
throw SyntaxError("undefined struct member", position)
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte // 1 arg, type = anything, result type = ubyte
if(args.size!=1) if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position) throw SyntaxError("sizeof requires one argument", position)
@ -287,17 +317,17 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
val dt = args[0].inferType(program) val dt = args[0].inferType(program)
if(dt.isKnown) { if(dt.isKnown) {
val target = (args[0] as IdentifierReference).targetStatement(program.namespace) val target = (args[0] as IdentifierReference).targetStatement(program)
?: throw CannotEvaluateException("sizeof", "no target") ?: throw CannotEvaluateException("sizeof", "no target")
fun structSize(target: StructDecl) = fun structSize(target: StructDecl) =
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position) NumericLiteralValue(DataType.UBYTE, target.statements.map { memsizer.memorySize((it as VarDecl).datatype) }.sum(), position)
return when { return when {
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> { dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size") val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT)) val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
numericLiteral(elementDt.memorySize() * length, position) numericLiteral(memsizer.memorySize(elementDt) * length, position)
} }
dt.istype(DataType.STRUCT) -> { dt.istype(DataType.STRUCT) -> {
when (target) { when (target) {
@ -307,19 +337,20 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
} }
} }
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position) dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position) else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.STRUCT)), position)
} }
} else { } else {
throw SyntaxError("sizeof invalid argument type", position) throw SyntaxError("sizeof invalid argument type", position)
} }
} }
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinLen(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE. // note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1) if(args.size!=1)
throw SyntaxError("len requires one argument", position) throw SyntaxError("len requires one argument", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace) val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
var arraySize = directMemVar?.arraysize?.constIndex() var arraySize = directMemVar?.arraysize?.constIndex()
if(arraySize != null) if(arraySize != null)
return NumericLiteralValue.optimalInteger(arraySize, position) return NumericLiteralValue.optimalInteger(arraySize, position)
@ -327,7 +358,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position) return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
if(args[0] !is IdentifierReference) if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier", position) throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace) val target = (args[0] as IdentifierReference).targetVarDecl(program)
?: throw CannotEvaluateException("len", "no target vardecl") ?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) { return when(target.datatype) {
@ -338,7 +369,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
NumericLiteralValue.optimalInteger(arraySize, args[0].position) NumericLiteralValue.optimalInteger(arraySize, args[0].position)
} }
DataType.STR -> { DataType.STR -> {
val refLv = target.value as StringLiteralValue val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown")
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position) NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
} }
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position) DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
@ -348,7 +379,8 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
} }
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinMkword(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 2) if (args.size != 2)
throw SyntaxError("mkword requires msb and lsb arguments", position) throw SyntaxError("mkword requires msb and lsb arguments", position)
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException() val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -357,7 +389,8 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, result, position) return NumericLiteralValue(DataType.UWORD, result, position)
} }
private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinSin8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin8 requires one argument", position) throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -365,7 +398,8 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position) return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
} }
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin8u requires one argument", position) throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -373,7 +407,8 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position) return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
} }
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinCos8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos8 requires one argument", position) throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -381,7 +416,8 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position) return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
} }
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos8u requires one argument", position) throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -389,7 +425,8 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position) return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
} }
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinSin16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin16 requires one argument", position) throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -397,7 +434,8 @@ private fun builtinSin16(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position) return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
} }
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin16u requires one argument", position) throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -405,7 +443,8 @@ private fun builtinSin16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position) return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
} }
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinCos16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos16 requires one argument", position) throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -413,7 +452,8 @@ private fun builtinCos16(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position) return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
} }
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos16u requires one argument", position) throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -421,7 +461,8 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position) return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
} }
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinSgn(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sgn requires one argument", position) throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()

View File

@ -1,47 +0,0 @@
package prog8.compiler.target
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.nio.file.Path
internal interface CompilationTarget {
val name: String
val machine: IMachineDefinition
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator
companion object {
lateinit var instance: CompilationTarget
}
}
internal object C64Target: CompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
AsmGen(program, errors, zp, options, path)
}
internal object Cx16Target: CompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
AsmGen(program, errors, zp, options, path)
}

View File

@ -0,0 +1,118 @@
package prog8.compiler.target
import prog8.ast.IMemSizer
import prog8.ast.IStringEncoding
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.AssignTarget
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.nio.file.Path
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun isInRegularRAM(target: AssignTarget, program: Program): Boolean {
val memAddr = target.memoryAddress
val arrayIdx = target.arrayindexed
val ident = target.identifier
when {
memAddr != null -> {
return when (memAddr.addressExpression) {
is NumericLiteralValue -> {
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
arrayIdx != null -> {
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
ident != null -> {
val decl = ident.targetVarDecl(program)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
}
}
}
internal object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal fun asmGeneratorFor(
compTarget: ICompilationTarget,
program: Program,
errors: IErrorReporter,
zp: Zeropage,
options: CompilationOptions,
outputDir: Path
): IAssemblyGenerator
{
return AsmGen(program, errors, zp, options, compTarget, outputDir)
}

View File

@ -1,22 +1,20 @@
package prog8.compiler.target package prog8.compiler.target
import prog8.ast.Program
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage import prog8.compiler.Zeropage
import prog8.parser.ModuleImporter
internal interface IMachineFloat { interface IMachineFloat {
fun toDouble(): Double fun toDouble(): Double
fun makeFloatFillAsm(): String fun makeFloatFillAsm(): String
} }
internal enum class CpuType { enum class CpuType {
CPU6502, CPU6502,
CPU65c02 CPU65c02
} }
internal interface IMachineDefinition { interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int val FLOAT_MEM_SIZE: Int
@ -32,7 +30,8 @@ internal interface IMachineDefinition {
fun initializeZeropage(compilerOptions: CompilationOptions) fun initializeZeropage(compilerOptions: CompilationOptions)
fun getFloat(num: Number): IMachineFloat fun getFloat(num: Number): IMachineFloat
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
fun launchEmulator(programName: String) fun launchEmulator(programName: String)
fun isRegularRAMaddress(address: Int): Boolean fun isRegularRAMaddress(address: Int): Boolean
} }

View File

@ -2,13 +2,12 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType import prog8.compiler.OutputType
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.IAssemblyProgram import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path import java.nio.file.Path
import kotlin.system.exitProcess import kotlin.system.exitProcess
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram { class AssemblyProgram(override val name: String, outputDir: Path, private val compTarget: String) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm") private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg") private val prgFile = outputDir.resolve("$name.prg")
private val binFile = outputDir.resolve("$name.bin") private val binFile = outputDir.resolve("$name.bin")
@ -23,12 +22,12 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
val outFile = when (options.output) { val outFile = when (options.output) {
OutputType.PRG -> { OutputType.PRG -> {
command.add("--cbm-prg") command.add("--cbm-prg")
println("\nCreating prg for target ${CompilationTarget.instance.name}.") println("\nCreating prg for target $compTarget.")
prgFile prgFile
} }
OutputType.RAW -> { OutputType.RAW -> {
command.add("--nostart") command.add("--nostart")
println("\nCreating raw binary for target ${CompilationTarget.instance.name}.") println("\nCreating raw binary for target $compTarget.")
binFile binFile
} }
} }

View File

@ -1,11 +1,9 @@
package prog8.compiler.target.c64 package prog8.compiler.target.c64
import prog8.ast.Program
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.CpuType import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat import prog8.compiler.target.IMachineFloat
import prog8.parser.ModuleImporter
import java.io.IOException import java.io.IOException
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.pow import kotlin.math.pow
@ -30,9 +28,11 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num) override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) { override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib") listOf("syslib")
else
emptyList()
} }
override fun launchEmulator(programName: String) { override fun launchEmulator(programName: String) {
@ -102,7 +102,7 @@ internal object C64MachineDefinition: IMachineDefinition {
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area' // 0x90-0xfa is 'kernal work storage area'
)) ))
} }

View File

@ -172,7 +172,7 @@ object Petscii {
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK '\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK '\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE '\u2592', // ▒ 0xA6 -> MEDIUM SHADE
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
@ -236,7 +236,7 @@ object Petscii {
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK '\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK '\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE '\u2592', // ▒ 0xE6 -> MEDIUM SHADE
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
@ -431,7 +431,7 @@ object Petscii {
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK '\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK '\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE '\u2592', // ▒ 0xA6 -> MEDIUM SHADE
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
@ -495,7 +495,7 @@ object Petscii {
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK '\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK '\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE '\u2592', // ▒ 0xE6 -> MEDIUM SHADE
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
@ -626,7 +626,7 @@ object Petscii {
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK '\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK '\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0x66 -> MEDIUM SHADE '\u2592', // ▒ 0x66 -> MEDIUM SHADE
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
@ -885,7 +885,7 @@ object Petscii {
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK '\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK '\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0x66 -> MEDIUM SHADE '\u2592', // ▒ 0x66 -> MEDIUM SHADE
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK

View File

@ -1,28 +1,21 @@
package prog8.compiler.target.c64.codegen package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall import prog8.ast.*
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.antlr.escape import prog8.ast.antlr.escape
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.FSignature
import prog8.compiler.target.* import prog8.compiler.target.*
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
import prog8.compiler.target.IAssemblyGenerator
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.c64.AssemblyProgram import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.codegen.assignment.AssignmentAsmGen import prog8.compiler.target.c64.codegen.assignment.AssignmentAsmGen
import prog8.compiler.target.generatedLabelPrefix
import prog8.functions.BuiltinFunctions
import prog8.functions.FSignature
import java.io.CharConversionException import java.io.CharConversionException
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.* import java.util.*
@ -30,9 +23,10 @@ import kotlin.math.absoluteValue
internal class AsmGen(private val program: Program, internal class AsmGen(private val program: Program,
val errors: ErrorReporter, val errors: IErrorReporter,
val zeropage: Zeropage, val zeropage: Zeropage,
val options: CompilationOptions, val options: CompilationOptions,
private val compTarget: ICompilationTarget,
private val outputDir: Path): IAssemblyGenerator { private val outputDir: Path): IAssemblyGenerator {
// for expressions and augmented assignments: // for expressions and augmented assignments:
@ -43,12 +37,12 @@ internal class AsmGen(private val program: Program,
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname) private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
private val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>() private val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
private val breakpointLabels = mutableListOf<String>() private val breakpointLabels = mutableListOf<String>()
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this)
private val forloopsAsmGen = ForLoopsAsmGen(program, this) private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this) private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
private val functioncallAsmGen = FunctionCallAsmGen(program, this) private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this) private val expressionsAsmGen = ExpressionsAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen) private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
internal val loopEndLabels = ArrayDeque<String>() internal val loopEndLabels = ArrayDeque<String>()
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>() private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
internal val slabs = mutableMapOf<String, Int>() internal val slabs = mutableMapOf<String, Int>()
@ -92,15 +86,17 @@ internal class AsmGen(private val program: Program,
} }
} }
return AssemblyProgram(program.name, outputDir) return AssemblyProgram(program.name, outputDir, compTarget.name)
} }
internal fun isTargetCpu(cpu: CpuType) = compTarget.machine.cpu == cpu
internal fun haveFPWR() = compTarget is Cx16Target
private fun header() { private fun header() {
val ourName = this.javaClass.name val ourName = this.javaClass.name
val cpu = when(CompilationTarget.instance.machine.cpu) { val cpu = when(compTarget.machine.cpu) {
CpuType.CPU6502 -> "6502" CpuType.CPU6502 -> "6502"
CpuType.CPU65c02 -> "65c02" CpuType.CPU65c02 -> "w65c02"
else -> "unsupported" else -> "unsupported"
} }
@ -113,16 +109,16 @@ internal class AsmGen(private val program: Program,
program.actualLoadAddress = program.definedLoadAddress program.actualLoadAddress = program.definedLoadAddress
if (program.actualLoadAddress == 0) // fix load address if (program.actualLoadAddress == 0) // fix load address
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC) program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
CompilationTarget.instance.machine.BASIC_LOAD_ADDRESS else CompilationTarget.instance.machine.RAW_LOAD_ADDRESS compTarget.machine.BASIC_LOAD_ADDRESS else compTarget.machine.RAW_LOAD_ADDRESS
// the global prog8 variables needed // the global prog8 variables needed
val zp = CompilationTarget.instance.machine.zeropage val zp = compTarget.machine.zeropage
out("P8ZP_SCRATCH_B1 = ${zp.SCRATCH_B1}") out("P8ZP_SCRATCH_B1 = ${zp.SCRATCH_B1}")
out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}") out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word") out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word") out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
out("P8ESTACK_LO = ${CompilationTarget.instance.machine.ESTACK_LO.toHex()}") out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
out("P8ESTACK_HI = ${CompilationTarget.instance.machine.ESTACK_HI.toHex()}") out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
when { when {
options.launcher == LauncherType.BASIC -> { options.launcher == LauncherType.BASIC -> {
@ -136,13 +132,15 @@ internal class AsmGen(private val program: Program,
out("+\t.word 0") out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n") out("_prog8_entrypoint\t; assembly code starts here\n")
if(!options.noSysInit) if(!options.noSysInit)
out(" jsr ${CompilationTarget.instance.name}.init_system") out(" jsr ${compTarget.name}.init_system")
out(" jsr ${compTarget.name}.init_system_phase2")
} }
options.output == OutputType.PRG -> { options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----") out("; ---- program without basic sys call ----")
out("* = ${program.actualLoadAddress.toHex()}\n") out("* = ${program.actualLoadAddress.toHex()}\n")
if(!options.noSysInit) if(!options.noSysInit)
out(" jsr ${CompilationTarget.instance.name}.init_system") out(" jsr ${compTarget.name}.init_system")
out(" jsr ${compTarget.name}.init_system_phase2")
} }
options.output == OutputType.RAW -> { options.output == OutputType.RAW -> {
out("; ---- raw assembler program ----") out("; ---- raw assembler program ----")
@ -174,7 +172,7 @@ internal class AsmGen(private val program: Program,
// the global list of all floating point constants for the whole program // the global list of all floating point constants for the whole program
out("; global float constants") out("; global float constants")
for (flt in globalFloatConsts) { for (flt in globalFloatConsts) {
val floatFill = CompilationTarget.instance.machine.getFloat(flt.key).makeFloatFillAsm() val floatFill = compTarget.machine.getFloat(flt.key).makeFloatFillAsm()
val floatvalue = flt.key val floatvalue = flt.key
out("${flt.value}\t.byte $floatFill ; float $floatvalue") out("${flt.value}\t.byte $floatFill ; float $floatvalue")
} }
@ -183,13 +181,17 @@ internal class AsmGen(private val program: Program,
private fun block2asm(block: Block) { private fun block2asm(block: Block) {
out("\n\n; ---- block: '${block.name}' ----") out("\n\n; ---- block: '${block.name}' ----")
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n")) if(block.address!=null)
out("* = ${block.address!!.toHex()}")
if(block.address!=null) { else {
out(".cerror * > ${block.address.toHex()}, 'block address overlaps by ', *-${block.address.toHex()},' bytes'") if("align_word" in block.options())
out("* = ${block.address.toHex()}") out("\t.align 2")
else if("align_page" in block.options())
out("\t.align $100")
} }
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
outputSourceLine(block) outputSourceLine(block)
zeropagevars2asm(block.statements) zeropagevars2asm(block.statements)
memdefs2asm(block.statements) memdefs2asm(block.statements)
@ -268,7 +270,7 @@ internal class AsmGen(private val program: Program,
try { try {
val errors = ErrorReporter() val errors = ErrorReporter()
val address = zeropage.allocate(fullName, variable.datatype, null, errors) val address = zeropage.allocate(fullName, variable.datatype, null, errors)
errors.handle() errors.report()
out("${variable.name} = $address\t; auto zp ${variable.datatype}") out("${variable.name} = $address\t; auto zp ${variable.datatype}")
// make sure we add the var to the set of zpvars for this block // make sure we add the var to the set of zpvars for this block
allocatedZeropageVariables[fullName] = address to variable.datatype allocatedZeropageVariables[fullName] = address to variable.datatype
@ -344,7 +346,7 @@ internal class AsmGen(private val program: Program,
} }
val floatFills = array.map { val floatFills = array.map {
val number = (it as NumericLiteralValue).number val number = (it as NumericLiteralValue).number
CompilationTarget.instance.machine.getFloat(number).makeFloatFillAsm() compTarget.machine.getFloat(number).makeFloatFillAsm()
} }
out(name) out(name)
for (f in array.zip(floatFills)) for (f in array.zip(floatFills))
@ -354,7 +356,7 @@ internal class AsmGen(private val program: Program,
} }
private fun memdefs2asm(statements: List<Statement>) { private fun memdefs2asm(statements: List<Statement>) {
out("\n; memdefs and kernel subroutines") out("\n; memdefs and kernal subroutines")
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST } val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
for(m in memvars) { for(m in memvars) {
if(m.value is NumericLiteralValue) if(m.value is NumericLiteralValue)
@ -364,10 +366,11 @@ internal class AsmGen(private val program: Program,
} }
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine } val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
for(sub in asmSubs) { for(sub in asmSubs) {
if(sub.asmAddress!=null) { val addr = sub.asmAddress
if(addr!=null) {
if(sub.statements.isNotEmpty()) if(sub.statements.isNotEmpty())
throw AssemblyError("kernel subroutine cannot have statements") throw AssemblyError("kernal subroutine cannot have statements")
out(" ${sub.name} = ${sub.asmAddress.toHex()}") out(" ${sub.name} = ${addr.toHex()}")
} }
} }
} }
@ -404,8 +407,9 @@ internal class AsmGen(private val program: Program,
} }
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) { private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
val string = (lastvar.value as StringLiteralValue).value val sv = lastvar.value as StringLiteralValue
out("${lastvar.name}\t; ${lastvar.datatype} \"${escape(string).replace("\u0000", "<NULL>")}\"") val altEncoding = if(sv.altEncoding) "@" else ""
out("${lastvar.name}\t; ${lastvar.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') } val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
for (chunk in outputBytes.chunked(16)) for (chunk in outputBytes.chunked(16))
out(" .byte " + chunk.joinToString()) out(" .byte " + chunk.joinToString())
@ -433,10 +437,10 @@ internal class AsmGen(private val program: Program,
"$" + it.number.toInt().toString(16).padStart(4, '0') "$" + it.number.toInt().toString(16).padStart(4, '0')
} }
is AddressOf -> { is AddressOf -> {
it.identifier.firstStructVarName(program.namespace) ?: asmSymbolName(it.identifier) it.identifier.firstStructVarName(program) ?: asmSymbolName(it.identifier)
} }
is IdentifierReference -> { is IdentifierReference -> {
it.firstStructVarName(program.namespace) ?: asmSymbolName(it) it.firstStructVarName(program) ?: asmSymbolName(it)
} }
else -> throw AssemblyError("weird array elt dt") else -> throw AssemblyError("weird array elt dt")
} }
@ -498,17 +502,23 @@ internal class AsmGen(private val program: Program,
} }
internal fun asmSymbolName(identifier: IdentifierReference): String { internal fun asmSymbolName(identifier: IdentifierReference): String {
return if(identifier.memberOfStruct(program.namespace)!=null) { return if(identifier.memberOfStruct(program)!=null) {
val name = identifier.targetVarDecl(program.namespace)!!.name val name = identifier.targetVarDecl(program)!!.name
fixNameSymbols(name) fixNameSymbols(name)
} else { } else {
fixNameSymbols(identifier.nameInSource.joinToString(".")) fixNameSymbols(identifier.nameInSource.joinToString("."))
} }
} }
internal fun asmSymbolName(regs: RegisterOrPair): String =
if(regs in Cx16VirtualRegisters)
"cx16." + regs.toString().toLowerCase()
else
throw AssemblyError("no symbol name for register $regs")
internal fun asmVariableName(identifier: IdentifierReference): String { internal fun asmVariableName(identifier: IdentifierReference): String {
return if(identifier.memberOfStruct(program.namespace)!=null) { return if(identifier.memberOfStruct(program)!=null) {
val name = identifier.targetVarDecl(program.namespace)!!.name val name = identifier.targetVarDecl(program)!!.name
fixNameSymbols(name) fixNameSymbols(name)
} else { } else {
fixNameSymbols(identifier.nameInSource.joinToString(".")) fixNameSymbols(identifier.nameInSource.joinToString("."))
@ -524,9 +534,24 @@ internal class AsmGen(private val program: Program,
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> { internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
// returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary) // returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary)
val sourceName = asmVariableName(pointervar) val sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program.namespace)!! val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name) val scopedName = vardecl.makeScopedName(vardecl.name)
return if(scopedName in allocatedZeropageVariables) { if (isTargetCpu(CpuType.CPU65c02)) {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" lda ($sourceName)")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda (P8ZP_SCRATCH_W1)""")
Pair(false, sourceName)
}
} else {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy // pointervar is already in the zero page, no need to copy
out(" ldy #0 | lda ($sourceName),y") out(" ldy #0 | lda ($sourceName),y")
Pair(true, sourceName) Pair(true, sourceName)
@ -541,32 +566,12 @@ internal class AsmGen(private val program: Program,
Pair(false, sourceName) Pair(false, sourceName)
} }
} }
fun storeByteIntoPointer(pointervar: IdentifierReference, ldaInstructionArg: String?) {
val sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program.namespace)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if(scopedName in allocatedZeropageVariables) {
// pointervar is already in the zero page, no need to copy
if(ldaInstructionArg!=null)
out(" lda $ldaInstructionArg")
out(" ldy #0 | sta ($sourceName),y")
} else {
out("""
ldy $sourceName
sty P8ZP_SCRATCH_W2
ldy $sourceName+1
sty P8ZP_SCRATCH_W2+1
${if(ldaInstructionArg==null) "" else "lda $ldaInstructionArg"}
ldy #0
sta (P8ZP_SCRATCH_W2),y""")
}
} }
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
internal fun saveRegisterLocal(register: CpuRegister, scope: Subroutine) { internal fun saveRegisterLocal(register: CpuRegister, scope: Subroutine) {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) { if (isTargetCpu(CpuType.CPU65c02)) {
// just use the cpu's stack for all registers, shorter code // just use the cpu's stack for all registers, shorter code
when (register) { when (register) {
CpuRegister.A -> out(" pha") CpuRegister.A -> out(" pha")
@ -595,7 +600,7 @@ internal class AsmGen(private val program: Program,
when (register) { when (register) {
CpuRegister.A -> out(" pha") CpuRegister.A -> out(" pha")
CpuRegister.X -> { CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx") if (isTargetCpu(CpuType.CPU65c02)) out(" phx")
else { else {
if(keepA) if(keepA)
out(" sta P8ZP_SCRATCH_REG | txa | pha | lda P8ZP_SCRATCH_REG") out(" sta P8ZP_SCRATCH_REG | txa | pha | lda P8ZP_SCRATCH_REG")
@ -604,7 +609,7 @@ internal class AsmGen(private val program: Program,
} }
} }
CpuRegister.Y -> { CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy") if (isTargetCpu(CpuType.CPU65c02)) out(" phy")
else { else {
if(keepA) if(keepA)
out(" sta P8ZP_SCRATCH_REG | tya | pha | lda P8ZP_SCRATCH_REG") out(" sta P8ZP_SCRATCH_REG | tya | pha | lda P8ZP_SCRATCH_REG")
@ -616,7 +621,7 @@ internal class AsmGen(private val program: Program,
} }
internal fun restoreRegisterLocal(register: CpuRegister) { internal fun restoreRegisterLocal(register: CpuRegister) {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) { if (isTargetCpu(CpuType.CPU65c02)) {
when (register) { when (register) {
// this just used the stack, for all registers. Shorter code. // this just used the stack, for all registers. Shorter code.
CpuRegister.A -> out(" pla") CpuRegister.A -> out(" pla")
@ -641,7 +646,7 @@ internal class AsmGen(private val program: Program,
out(" pla") out(" pla")
} }
CpuRegister.X -> { CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx") if (isTargetCpu(CpuType.CPU65c02)) out(" plx")
else { else {
if(keepA) if(keepA)
out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG") out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
@ -650,7 +655,7 @@ internal class AsmGen(private val program: Program,
} }
} }
CpuRegister.Y -> { CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply") if (isTargetCpu(CpuType.CPU65c02)) out(" ply")
else { else {
if(keepA) if(keepA)
out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG") out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
@ -710,7 +715,7 @@ internal class AsmGen(private val program: Program,
val reg = register.toString().toLowerCase() val reg = register.toString().toLowerCase()
val indexnum = expr.indexer.constIndex() val indexnum = expr.indexer.constIndex()
if(indexnum!=null) { if(indexnum!=null) {
val indexValue = indexnum * elementDt.memorySize() + if(addOneExtra) 1 else 0 val indexValue = indexnum * compTarget.memorySize(elementDt) + if(addOneExtra) 1 else 0
out(" ld$reg #$indexValue") out(" ld$reg #$indexValue")
return return
} }
@ -736,7 +741,7 @@ internal class AsmGen(private val program: Program,
} }
} }
DataType.FLOAT -> { DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5) require(compTarget.memorySize(DataType.FLOAT)==5)
out(""" out("""
lda $indexName lda $indexName
asl a asl a
@ -763,7 +768,7 @@ internal class AsmGen(private val program: Program,
} }
} }
DataType.FLOAT -> { DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5) require(compTarget.memorySize(DataType.FLOAT)==5)
out(""" out("""
lda $indexName lda $indexName
asl a asl a
@ -787,8 +792,8 @@ internal class AsmGen(private val program: Program,
internal fun translateExpression(indexer: ArrayIndex) = internal fun translateExpression(indexer: ArrayIndex) =
expressionsAsmGen.translateExpression(indexer) expressionsAsmGen.translateExpression(indexer)
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean) = internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack) builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack, resultRegister)
internal fun translateFunctionCall(functionCall: FunctionCall) = internal fun translateFunctionCall(functionCall: FunctionCall) =
functioncallAsmGen.translateFunctionCall(functionCall) functioncallAsmGen.translateFunctionCall(functionCall)
@ -967,16 +972,14 @@ internal class AsmGen(private val program: Program,
} }
} }
is IdentifierReference -> { is IdentifierReference -> {
val vardecl = (stmt.iterations as IdentifierReference).targetStatement(program.namespace) as VarDecl val vardecl = (stmt.iterations as IdentifierReference).targetStatement(program) as VarDecl
val name = asmVariableName(stmt.iterations as IdentifierReference) val name = asmVariableName(stmt.iterations as IdentifierReference)
when(vardecl.datatype) { when(vardecl.datatype) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
out(" lda $name") repeatByteCountVar(name, repeatLabel, endLabel, stmt.body)
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
} }
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
out(" lda $name | ldy $name+1") repeatWordCountVar(name, repeatLabel, endLabel, stmt.body)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
} }
else -> throw AssemblyError("invalid loop variable datatype $vardecl") else -> throw AssemblyError("invalid loop variable datatype $vardecl")
} }
@ -1006,6 +1009,7 @@ internal class AsmGen(private val program: Program,
if(constIterations==0) if(constIterations==0)
return return
// note: A/Y must have been loaded with the number of iterations already! // note: A/Y must have been loaded with the number of iterations already!
// TODO can be even more optimized by iterating over pages
val counterVar = makeLabel("repeatcounter") val counterVar = makeLabel("repeatcounter")
out(""" out("""
sta $counterVar sta $counterVar
@ -1040,22 +1044,54 @@ $counterVar .word 0""")
val counterVar = makeLabel("repeatcounter") val counterVar = makeLabel("repeatcounter")
if(constIterations==null) if(constIterations==null)
out(" beq $endLabel") out(" beq $endLabel")
out(""" out(" sta $counterVar")
sta $counterVar out(repeatLabel)
$repeatLabel""")
translate(body) translate(body)
out(""" out("""
dec $counterVar dec $counterVar
bne $repeatLabel bne $repeatLabel
beq $endLabel""") beq $endLabel
if(constIterations!=null && constIterations>=16 && zeropage.available() > 0) {
// allocate count var on ZP
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, body.position, errors)
out("""$counterVar = $zpAddr ; auto zp UBYTE""")
} else {
out("""
$counterVar .byte 0""") $counterVar .byte 0""")
out(endLabel)
} }
private fun repeatByteCountVar(repeatCountVar: String, repeatLabel: String, endLabel: String, body: AnonymousScope) {
// note: cannot use original counter variable because it should retain its original value
val counterVar = makeLabel("repeatcounter")
out(" lda $repeatCountVar | beq $endLabel | sta $counterVar")
out(repeatLabel)
translate(body)
out(" dec $counterVar | bne $repeatLabel")
// inline countervar:
out("""
beq $endLabel
$counterVar .byte 0""")
out(endLabel)
}
private fun repeatWordCountVar(repeatCountVar: String, repeatLabel: String, endLabel: String, body: AnonymousScope) {
// TODO can be even more optimized by iterating over pages
// note: cannot use original counter variable because it should retain its original value
val counterVar = makeLabel("repeatcounter")
out("""
lda $repeatCountVar
sta $counterVar
ora $repeatCountVar+1
beq $endLabel
lda $repeatCountVar+1
sta $counterVar+1""")
out(repeatLabel)
translate(body)
out("""
lda $counterVar
bne +
dec $counterVar+1
+ dec $counterVar
lda $counterVar
ora $counterVar+1
bne $repeatLabel
beq $endLabel
$counterVar .word 0""")
out(endLabel) out(endLabel)
} }
@ -1227,7 +1263,9 @@ $counterVar .byte 0""")
"%asmbinary" -> { "%asmbinary" -> {
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else "" val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else "" val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
out(" .binary \"${stmt.args[0].str}\" $offset $length") val includedSourcePath = stmt.definingModule().source.resolveSibling(stmt.args[0].str)
val relPath = Paths.get("").relativize(includedSourcePath)
out(" .binary \"./$relPath\" $offset $length")
} }
"%breakpoint" -> { "%breakpoint" -> {
val label = "_prog8_breakpoint_${breakpointLabels.size+1}" val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
@ -1244,17 +1282,20 @@ $label nop""")
} }
private fun getJumpTarget(jmp: Jump): String { private fun getJumpTarget(jmp: Jump): String {
val ident = jmp.identifier
val label = jmp.generatedLabel
val addr = jmp.address
return when { return when {
jmp.identifier!=null -> { ident!=null -> {
val target = jmp.identifier.targetStatement(program.namespace) val target = ident.targetStatement(program)
val asmName = asmSymbolName(jmp.identifier) val asmName = asmSymbolName(ident)
if(target is Label) if(target is Label)
"_$asmName" // prefix with underscore to jump to local label "_$asmName" // prefix with underscore to jump to local label
else else
asmName asmName
} }
jmp.generatedLabel!=null -> jmp.generatedLabel label!=null -> label
jmp.address!=null -> jmp.address.toHex() addr!=null -> addr.toHex()
else -> "????" else -> "????"
} }
} }
@ -1269,12 +1310,12 @@ $label nop""")
when (returnType) { when (returnType) {
in NumericDatatypes -> { in NumericDatatypes -> {
assignExpressionToRegister(returnvalue, returnReg.registerOrPair) assignExpressionToRegister(returnvalue, returnReg.registerOrPair!!)
} }
else -> { else -> {
// all else take its address and assign that also to AY register pair // all else take its address and assign that also to AY register pair
val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position) val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position)
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair) assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair!!)
} }
} }
} }
@ -1299,7 +1340,7 @@ $label nop""")
// sign extend signed byte on stack to signed word on stack // sign extend signed byte on stack to signed word on stack
when(valueDt) { when(valueDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(isTargetCpu(CpuType.CPU65c02))
out(" stz P8ESTACK_HI+1,x") out(" stz P8ESTACK_HI+1,x")
else else
out(" lda #0 | sta P8ESTACK_HI+1,x") out(" lda #0 | sta P8ESTACK_HI+1,x")
@ -1313,7 +1354,7 @@ $label nop""")
// sign extend signed byte in a var to a full word in that variable // sign extend signed byte in a var to a full word in that variable
when(valueDt) { when(valueDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(isTargetCpu(CpuType.CPU65c02))
out(" stz $asmvar+1") out(" stz $asmvar+1")
else else
out(" lda #0 | sta $asmvar+1") out(" lda #0 | sta $asmvar+1")
@ -1329,4 +1370,96 @@ $label nop""")
else -> throw AssemblyError("need byte type") else -> throw AssemblyError("need byte type")
} }
} }
internal fun isZpVar(scopedName: String): Boolean = scopedName in allocatedZeropageVariables
internal fun isZpVar(variable: IdentifierReference): Boolean {
val vardecl = variable.targetVarDecl(program)!!
return vardecl.makeScopedName(vardecl.name) in allocatedZeropageVariables
}
internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: Expression): Pair<Expression, Expression>? {
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {
val leftDt = pointerOffsetExpr.left.inferType(program)
val rightDt = pointerOffsetExpr.left.inferType(program)
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UBYTE))
return Pair(pointerOffsetExpr.left, pointerOffsetExpr.right)
if(leftDt.istype(DataType.UBYTE) && rightDt.istype(DataType.UWORD))
return Pair(pointerOffsetExpr.right, pointerOffsetExpr.left)
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UWORD)) {
// could be that the index was a constant numeric byte but converted to word, check that
val constIdx = pointerOffsetExpr.right.constValue(program)
if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) {
return Pair(pointerOffsetExpr.left, NumericLiteralValue(DataType.UBYTE, constIdx.number, constIdx.position))
}
// could be that the index was type casted into uword, check that
val rightTc = pointerOffsetExpr.right as? TypecastExpression
if(rightTc!=null && rightTc.expression.inferType(program).istype(DataType.UBYTE))
return Pair(pointerOffsetExpr.left, rightTc.expression)
val leftTc = pointerOffsetExpr.left as? TypecastExpression
if(leftTc!=null && leftTc.expression.inferType(program).istype(DataType.UBYTE))
return Pair(pointerOffsetExpr.right, leftTc.expression)
}
}
return null
}
internal fun tryOptimizedPointerAccessWithA(expr: BinaryExpression, write: Boolean): Boolean {
// optimize pointer,indexregister if possible
fun evalBytevalueWillClobberA(expr: Expression): Boolean {
val dt = expr.inferType(program)
if(!dt.istype(DataType.UBYTE) && !dt.istype(DataType.BYTE))
return true
return when(expr) {
is IdentifierReference -> false
is NumericLiteralValue -> false
is DirectMemoryRead -> expr.addressExpression !is IdentifierReference && expr.addressExpression !is NumericLiteralValue
is TypecastExpression -> evalBytevalueWillClobberA(expr.expression)
else -> true
}
}
if(expr.operator=="+") {
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
if(ptrAndIndex!=null) {
val pointervar = ptrAndIndex.first as? IdentifierReference
if(write) {
if(pointervar!=null && isZpVar(pointervar)) {
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
}
} else {
if(pointervar!=null && isZpVar(pointervar)) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (P8ZP_SCRATCH_W2),y")
}
}
return true
}
}
return false
}
} }

View File

@ -5,28 +5,28 @@ import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource import prog8.compiler.functions.FSignature
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment import prog8.compiler.target.c64.codegen.assignment.*
import prog8.compiler.target.c64.codegen.assignment.SourceStorageKind
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
import prog8.compiler.target.subroutineFloatEvalResultVar2 import prog8.compiler.target.subroutineFloatEvalResultVar2
import prog8.compiler.toHex
import prog8.functions.FSignature
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature, resultToStack: Boolean) { internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack) translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
} }
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) { internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false) translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
} }
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean) { private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if (discardResult && func.pure) if (discardResult && func.pure)
return // can just ignore the whole function call altogether return // can just ignore the whole function call altogether
@ -34,41 +34,45 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("cannot both discard the result AND put it onto stack") throw AssemblyError("cannot both discard the result AND put it onto stack")
val sscope = (fcall as Node).definingSubroutine() val sscope = (fcall as Node).definingSubroutine()
when (func.name) { when (func.name) {
"msb" -> funcMsb(fcall, resultToStack) "msb" -> funcMsb(fcall, resultToStack, resultRegister)
"lsb" -> funcLsb(fcall, resultToStack) "lsb" -> funcLsb(fcall, resultToStack, resultRegister)
"mkword" -> funcMkword(fcall, resultToStack) "mkword" -> funcMkword(fcall, resultToStack, resultRegister)
"abs" -> funcAbs(fcall, func, resultToStack, sscope) "abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
"swap" -> funcSwap(fcall) "swap" -> funcSwap(fcall)
"min", "max" -> funcMinMax(fcall, func, resultToStack) "min", "max" -> funcMinMax(fcall, func, resultToStack, resultRegister, sscope)
"sum" -> funcSum(fcall, resultToStack) "sum" -> funcSum(fcall, resultToStack, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, func, resultToStack) "any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
"sin8", "sin8u", "sin16", "sin16u", "sin8", "sin8u", "sin16", "sin16u",
"cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, sscope) "cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, sscope) "sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
"sin", "cos", "tan", "atan", "sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad", "ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil", "deg", "round", "floor", "ceil",
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, sscope) "rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, resultRegister, sscope)
"rnd", "rndw" -> funcRnd(func, resultToStack) "rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, sscope) "sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
"rol" -> funcRol(fcall) "rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall) "rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall) "ror" -> funcRor(fcall)
"ror2" -> funcRor2(fcall) "ror2" -> funcRor2(fcall)
"sort" -> funcSort(fcall) "sort" -> funcSort(fcall)
"reverse" -> funcReverse(fcall) "reverse" -> funcReverse(fcall)
"memory" -> funcMemory(fcall, discardResult, resultToStack) "memory" -> funcMemory(fcall, discardResult, resultToStack, resultRegister)
"peekw" -> funcPeekW(fcall, resultToStack, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
else -> TODO("missing asmgen for builtin func ${func.name}") else -> TODO("missing asmgen for builtin func ${func.name}")
} }
} }
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean) { private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult || fcall !is FunctionCall) if(discardResult || fcall !is FunctionCall)
throw AssemblyError("should not discard result of memory allocation at $fcall") throw AssemblyError("should not discard result of memory allocation at $fcall")
val scope = fcall.definingScope()
val nameRef = fcall.args[0] as IdentifierReference val nameRef = fcall.args[0] as IdentifierReference
val name = (nameRef.targetVarDecl(program.namespace)!!.value as StringLiteralValue).value val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
val size = (fcall.args[1] as NumericLiteralValue).number.toInt() val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
val existingSize = asmgen.slabs[name] val existingSize = asmgen.slabs[name]
@ -82,39 +86,43 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(resultToStack) if(resultToStack)
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null) AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
else else
AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen) AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, null, program, asmgen)
val assign = AsmAssignment(src, target, false, fcall.position) val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign) asmgen.translateNormalAssignment(assign)
// remove the variable for the name, it's not used as a variable only as a tag for the assembler.
val nameDecl = scope.statements.single { it is VarDecl && it.name==nameRef.nameInSource.single() }
asmgen.removals.add(Pair(nameDecl, scope))
asmgen.slabs[name] = size asmgen.slabs[name] = size
} }
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) { private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope) translateArguments(fcall.args, func, scope)
if(resultToStack) if(resultToStack)
asmgen.out(" jsr prog8_lib.func_sqrt16_stack") asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
else else {
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A") asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
} }
private fun funcSinCosInt(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) { private fun funcSinCosInt(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope) translateArguments(fcall.args, func, scope)
if(resultToStack) if(resultToStack)
asmgen.out(" jsr prog8_lib.func_${func.name}_stack") asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
else else
when(func.name) { when(func.name) {
"sin8", "sin8u", "cos8", "cos8u" -> asmgen.out(" jsr prog8_lib.func_${func.name}_into_A") "sin8", "sin8u", "cos8", "cos8u" -> {
"sin16", "sin16u", "cos16", "cos16u" -> asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY") asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
"sin16", "sin16u", "cos16", "cos16u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
} }
} }
private fun funcReverse(fcall: IFunctionCall) { private fun funcReverse(fcall: IFunctionCall) {
val variable = fcall.args.single() val variable = fcall.args.single()
if (variable is IdentifierReference) { if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!! val decl = variable.targetVarDecl(program)!!
val varName = asmgen.asmVariableName(variable) val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.constIndex() val numElements = decl.arraysize!!.constIndex()
when (decl.datatype) { when (decl.datatype) {
@ -153,7 +161,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcSort(fcall: IFunctionCall) { private fun funcSort(fcall: IFunctionCall) {
val variable = fcall.args.single() val variable = fcall.args.single()
if (variable is IdentifierReference) { if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!! val decl = variable.targetVarDecl(program)!!
val varName = asmgen.asmVariableName(variable) val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.constIndex() val numElements = decl.arraysize!!.constIndex()
when (decl.datatype) { when (decl.datatype) {
@ -239,6 +247,17 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if (what.addressExpression is NumericLiteralValue) { if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" ror ${number.toHex()}") asmgen.out(" ror ${number.toHex()}")
} else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
if(ptrAndIndex!=null) {
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
sta (+) + 1
sty (+) + 2
+ ror ${'$'}ffff,x ; modified""")
} else { } else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY) asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
@ -247,6 +266,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
+ ror ${'$'}ffff ; modified""") + ror ${'$'}ffff ; modified""")
} }
} }
}
is IdentifierReference -> { is IdentifierReference -> {
val variable = asmgen.asmVariableName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable") asmgen.out(" ror $variable")
@ -328,6 +348,17 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if (what.addressExpression is NumericLiteralValue) { if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" rol ${number.toHex()}") asmgen.out(" rol ${number.toHex()}")
} else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
if(ptrAndIndex!=null) {
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
sta (+) + 1
sty (+) + 2
+ rol ${'$'}ffff,x ; modified""")
} else { } else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY) asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
@ -336,6 +367,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
+ rol ${'$'}ffff ; modified""") + rol ${'$'}ffff ; modified""")
} }
} }
}
is IdentifierReference -> { is IdentifierReference -> {
val variable = asmgen.asmVariableName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable") asmgen.out(" rol $variable")
@ -366,15 +398,17 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.assignExpressionToVariable(indexerExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null) asmgen.assignExpressionToVariable(indexerExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
} }
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) { private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope) translateArguments(fcall.args, func, scope)
if(resultToStack) if(resultToStack)
asmgen.out(" jsr floats.func_${func.name}_stack") asmgen.out(" jsr floats.func_${func.name}_stack")
else else {
asmgen.out(" jsr floats.func_${func.name}_fac1") asmgen.out(" jsr floats.func_${func.name}_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
}
} }
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) { private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope) translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program) val dt = fcall.args.single().inferType(program)
if(resultToStack) { if(resultToStack) {
@ -395,10 +429,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A") DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
} }
} }
private fun funcAnyAll(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean) { private fun funcAnyAll(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0]) outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program) val dt = fcall.args.single().inferType(program)
if(resultToStack) { if(resultToStack) {
@ -415,10 +450,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A") DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
} }
} }
private fun funcMinMax(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean) { private fun funcMinMax(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0]) outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program) val dt = fcall.args.single().inferType(program)
if(resultToStack) { if(resultToStack) {
@ -432,17 +468,32 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} else { } else {
when (dt.typeOrElse(DataType.STRUCT)) { when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A") DataType.ARRAY_UB, DataType.STR -> {
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A") asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY") assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY") }
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_fac1") DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_${function.name}_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
} }
} }
private fun funcSum(fcall: IFunctionCall, resultToStack: Boolean) { private fun funcSum(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0]) outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program) val dt = fcall.args.single().inferType(program)
if(resultToStack) { if(resultToStack) {
@ -456,11 +507,26 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} else { } else {
when (dt.typeOrElse(DataType.STRUCT)) { when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY") DataType.ARRAY_UB, DataType.STR -> {
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_sum_b_into_AY") asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY") assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_sum_w_into_AY") }
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_sum_f_fac1") DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_sum_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt") else -> throw AssemblyError("weird type $dt")
} }
} }
@ -510,6 +576,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// optimized simple case: swap two memory locations // optimized simple case: swap two memory locations
if(first is DirectMemoryRead && second is DirectMemoryRead) { if(first is DirectMemoryRead && second is DirectMemoryRead) {
// TODO optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+1), @(ptr+2))
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex() val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex() val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
@ -582,12 +649,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val assignFirst = AsmAssignment( val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W2"), AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W2"),
targetFromExpr(first, datatype), targetFromExpr(first, datatype),
false, first.position false, program.memsizer, first.position
) )
val assignSecond = AsmAssignment( val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W1"), AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W1"),
targetFromExpr(second, datatype), targetFromExpr(second, datatype),
false, second.position false, program.memsizer, second.position
) )
asmgen.translateNormalAssignment(assignFirst) asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond) asmgen.translateNormalAssignment(assignSecond)
@ -599,12 +666,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val assignFirst = AsmAssignment( val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT), AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
targetFromExpr(first, datatype), targetFromExpr(first, datatype),
false, first.position false, program.memsizer, first.position
) )
val assignSecond = AsmAssignment( val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT), AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
targetFromExpr(second, datatype), targetFromExpr(second, datatype),
false, second.position false, program.memsizer, second.position
) )
asmgen.translateNormalAssignment(assignFirst) asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond) asmgen.translateNormalAssignment(assignSecond)
@ -614,8 +681,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) { private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val index1 = indexValue1.number.toInt() * elementDt.memorySize() val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
val index2 = indexValue2.number.toInt() * elementDt.memorySize() val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
when(elementDt) { when(elementDt) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
asmgen.out(""" asmgen.out("""
@ -728,7 +795,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) { private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) {
val index1 = indexValue1.number.toInt() * elementDt.memorySize() val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
val idxAsmName2 = asmgen.asmVariableName(indexName2) val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) { when(elementDt) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
@ -787,7 +854,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) { private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val idxAsmName1 = asmgen.asmVariableName(indexName1) val idxAsmName1 = asmgen.asmVariableName(indexName1)
val index2 = indexValue2.number.toInt() * elementDt.memorySize() val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
when(elementDt) { when(elementDt) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
asmgen.out(""" asmgen.out("""
@ -843,7 +910,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} }
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) { private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope) translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program).typeOrElse(DataType.STRUCT) val dt = fcall.args.single().inferType(program).typeOrElse(DataType.STRUCT)
if(resultToStack) { if(resultToStack) {
@ -855,40 +922,221 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} else { } else {
when (dt) { when (dt) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_into_A") in ByteDatatypes -> {
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w_into_AY") asmgen.out(" jsr prog8_lib.abs_b_into_A")
DataType.FLOAT -> asmgen.out(" jsr floats.abs_f_fac1") assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister!!, scope, program, asmgen), CpuRegister.A)
}
in WordDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister!!, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.FLOAT -> {
asmgen.out(" jsr floats.abs_f_fac1")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister!!, scope, program, asmgen), RegisterOrPair.FAC1)
}
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
} }
} }
} }
private fun funcRnd(func: FSignature, resultToStack: Boolean) { private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
when(func.name) { when(func.name) {
"rnd" -> { "rnd" -> {
if(resultToStack) if(resultToStack)
asmgen.out(" jsr prog8_lib.func_rnd_stack") asmgen.out(" jsr prog8_lib.func_rnd_stack")
else else {
asmgen.out(" jsr math.randbyte") asmgen.out(" jsr math.randbyte")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
} }
"rndw" -> { "rndw" -> {
if(resultToStack) if(resultToStack)
asmgen.out(" jsr prog8_lib.func_rndw_stack") asmgen.out(" jsr prog8_lib.func_rndw_stack")
else else {
asmgen.out(" jsr math.randword") asmgen.out(" jsr math.randword")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
} }
else -> throw AssemblyError("wrong func") else -> throw AssemblyError("wrong func")
} }
} }
private fun funcMkword(fcall: IFunctionCall, resultToStack: Boolean) { private fun funcPokeW(fcall: IFunctionCall) {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb when(val addrExpr = fcall.args[0]) {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb is NumericLiteralValue -> {
if(resultToStack) asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex") val addr = addrExpr.number.toHex()
asmgen.out(" sta $addr | sty ${addr}+1")
return
}
is IdentifierReference -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out("""
sta ($varname)
txa
ldy #1
sta ($varname),y""")
} else {
asmgen.out("""
ldy #0
sta ($varname),y
txa
iny
sta ($varname),y""")
}
asmgen.restoreRegisterLocal(CpuRegister.X)
return
}
}
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
sta ($varname),y
txa
iny
sta ($varname),y""")
asmgen.restoreRegisterLocal(CpuRegister.X)
return
}
}
} }
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean) { asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD, null)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_pokew")
}
private fun funcPeekW(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
when(val addrExpr = fcall.args[0]) {
is NumericLiteralValue -> {
val addr = addrExpr.number.toHex()
asmgen.out(" lda $addr | ldy ${addr}+1")
}
is IdentifierReference -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out("""
ldy #1
lda ($varname),y
tay
lda ($varname)""")
} else {
asmgen.out("""
ldy #0
lda ($varname),y
pha
iny
lda ($varname),y
tay
pla""")
}
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peekw")
}
}
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
lda ($varname),y
pha
iny
lda ($varname),y
tay
pla""")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peekw")
}
}
else -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peekw")
}
}
if(resultToStack){
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
} else {
when(resultRegister ?: RegisterOrPair.AY) {
RegisterOrPair.AY -> {}
RegisterOrPair.AX -> asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG")
RegisterOrPair.XY -> asmgen.out(" tax")
in Cx16VirtualRegisters -> asmgen.out(" sta cx16.${resultRegister.toString().toLowerCase()} | sty cx16.${resultRegister.toString().toLowerCase()}+1")
else -> throw AssemblyError("invalid reg")
}
}
}
private fun funcMkword(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(resultToStack) {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
} else {
val reg = resultRegister ?: RegisterOrPair.AY
var needAsave = !(fcall.args[0] is DirectMemoryRead || fcall.args[0] is NumericLiteralValue || fcall.args[0] is IdentifierReference)
if(!needAsave) {
val mr0 = fcall.args[0] as? DirectMemoryRead
val mr1 = fcall.args[1] as? DirectMemoryRead
if (mr0 != null)
needAsave = mr0.addressExpression !is NumericLiteralValue && mr0.addressExpression !is IdentifierReference
if (mr1 != null)
needAsave = needAsave or (mr1.addressExpression !is NumericLiteralValue && mr1.addressExpression !is IdentifierReference)
}
when(reg) {
RegisterOrPair.AX -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if(needAsave)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.X) // msb
if(needAsave)
asmgen.out(" pla")
}
RegisterOrPair.AY -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if(needAsave)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
if(needAsave)
asmgen.out(" pla")
}
RegisterOrPair.XY -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if(needAsave)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
if(needAsave)
asmgen.out(" pla")
asmgen.out(" tax")
}
in Cx16VirtualRegisters -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
asmgen.out(" sta cx16.${reg.toString().toLowerCase()}")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // msb
asmgen.out(" sta cx16.${reg.toString().toLowerCase()}+1")
}
else -> throw AssemblyError("invalid mkword target reg")
}
}
}
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single() val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes) if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("msb required word argument") throw AssemblyError("msb required word argument")
@ -896,19 +1144,43 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("msb(const) should have been const-folded away") throw AssemblyError("msb(const) should have been const-folded away")
if (arg is IdentifierReference) { if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg) val sourceName = asmgen.asmVariableName(arg)
asmgen.out(" lda $sourceName+1") if(resultToStack) {
if (resultToStack) asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
asmgen.out(" sta P8ESTACK_LO,x | dex")
} else { } else {
when(resultRegister) {
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
else -> throw AssemblyError("invalid reg")
}
}
} else {
if(resultToStack) {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY) asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
if (resultToStack)
asmgen.out(" tya | sta P8ESTACK_LO,x | dex") asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
else } else {
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.out(" tya") asmgen.out(" tya")
} }
RegisterOrPair.X -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
asmgen.out(" pla")
}
RegisterOrPair.Y -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.out(" pla")
}
else -> throw AssemblyError("invalid reg")
}
}
}
} }
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean) { private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single() val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes) if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("lsb required word argument") throw AssemblyError("lsb required word argument")
@ -917,16 +1189,45 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if (arg is IdentifierReference) { if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg) val sourceName = asmgen.asmVariableName(arg)
asmgen.out(" lda $sourceName") if(resultToStack) {
if (resultToStack) asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
asmgen.out(" sta P8ESTACK_LO,x | dex")
} else { } else {
when(resultRegister) {
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName")
else -> throw AssemblyError("invalid reg")
}
}
} else {
if(resultToStack) {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY) asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction. // NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
// this is required because the compiler assumes the status bits are set according to what A is (lsb) // this is required because the compiler assumes the status bits are set according to what A is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression. // and will not generate another cmp when lsb() is directly used inside a comparison expression.
if (resultToStack)
asmgen.out(" sta P8ESTACK_LO,x | dex") asmgen.out(" sta P8ESTACK_LO,x | dex")
} else {
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.X -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
// NOTE: we rely on the fact that the above assignment to XY, assigns the Lsb to X as the last instruction.
// this is required because the compiler assumes the status bits are set according to what X is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.Y -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.out(" tay | pla | cpy #0")
}
else -> throw AssemblyError("invalid reg")
}
}
} }
} }
@ -934,7 +1235,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// address in P8ZP_SCRATCH_W1, number of elements in A // address in P8ZP_SCRATCH_W1, number of elements in A
arg as IdentifierReference arg as IdentifierReference
val identifierName = asmgen.asmVariableName(arg) val identifierName = asmgen.asmVariableName(arg)
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.constIndex()!! val size = arg.targetVarDecl(program)!!.arraysize!!.constIndex()!!
asmgen.out(""" asmgen.out("""
lda #<$identifierName lda #<$identifierName
ldy #>$identifierName ldy #>$identifierName
@ -989,7 +1290,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} }
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, conv.dt, null, variableAsmName = varname) val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, conv.dt, null, variableAsmName = varname)
val assign = AsmAssignment(src, tgt, false, value.position) val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign) asmgen.translateNormalAssignment(assign)
} }
conv.reg != null -> { conv.reg != null -> {
@ -1005,7 +1306,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} }
val tgt = AsmAssignTarget.fromRegisters(conv.reg, null, program, asmgen) val tgt = AsmAssignTarget.fromRegisters(conv.reg, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, value.position) val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign) asmgen.translateNormalAssignment(assign)
} }
else -> throw AssemblyError("callconv") else -> throw AssemblyError("callconv")

View File

@ -6,12 +6,11 @@ import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.target.CpuType import prog8.compiler.target.CpuType
import prog8.compiler.target.subroutineFloatEvalResultVar1 import prog8.compiler.target.subroutineFloatEvalResultVar1
import prog8.compiler.toHex
import prog8.functions.BuiltinFunctions
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -1327,10 +1326,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateFunctionCallResultOntoStack(call: FunctionCall) { private fun translateFunctionCallResultOntoStack(call: FunctionCall) {
// only for use in nested expression evaluation // only for use in nested expression evaluation
val sub = call.target.targetStatement(program.namespace) val sub = call.target.targetStatement(program)
if(sub is BuiltinFunctionStatementPlaceholder) { if(sub is BuiltinFunctionStatementPlaceholder) {
val builtinFunc = BuiltinFunctions.getValue(sub.name) val builtinFunc = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(call, builtinFunc, true) asmgen.translateBuiltinFunctionCallExpression(call, builtinFunc, true, null)
} else { } else {
sub as Subroutine sub as Subroutine
asmgen.saveXbeforeCall(call) asmgen.saveXbeforeCall(call)
@ -1394,7 +1393,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
when(typecast.type) { when(typecast.type) {
DataType.UBYTE, DataType.BYTE -> {} DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI+1,x") asmgen.out(" stz P8ESTACK_HI+1,x")
else else
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x") asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
@ -1457,6 +1456,24 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
internal fun translateDirectMemReadExpression(expr: DirectMemoryRead, pushResultOnEstack: Boolean) { internal fun translateDirectMemReadExpression(expr: DirectMemoryRead, pushResultOnEstack: Boolean) {
fun assignViaExprEval() {
asmgen.assignExpressionToVariable(expr.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (pushResultOnEstack) {
asmgen.out(" lda (P8ZP_SCRATCH_W2) | dex | sta P8ESTACK_LO+1,x")
} else {
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
}
} else {
if (pushResultOnEstack) {
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y | dex | sta P8ESTACK_LO+1,x")
} else {
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
}
}
}
when(expr.addressExpression) { when(expr.addressExpression) {
is NumericLiteralValue -> { is NumericLiteralValue -> {
val address = (expr.addressExpression as NumericLiteralValue).number.toInt() val address = (expr.addressExpression as NumericLiteralValue).number.toInt()
@ -1470,14 +1487,15 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(pushResultOnEstack) if(pushResultOnEstack)
asmgen.out(" sta P8ESTACK_LO,x | dex") asmgen.out(" sta P8ESTACK_LO,x | dex")
} }
else -> { is BinaryExpression -> {
asmgen.assignExpressionToVariable(expr.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null) if(asmgen.tryOptimizedPointerAccessWithA(expr.addressExpression as BinaryExpression, false)) {
if(pushResultOnEstack) { if(pushResultOnEstack)
asmgen.out(" dex | ldy #0 | lda (P8ZP_SCRATCH_W2),y | sta P8ESTACK_LO+1,x") asmgen.out(" sta P8ESTACK_LO,x | dex")
} else { } else {
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y") assignViaExprEval()
} }
} }
else -> assignViaExprEval()
} }
} }
@ -1655,7 +1673,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
DataType.UWORD -> { DataType.UWORD -> {
if(amount>=16) { if(amount>=16) {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x") asmgen.out(" stz P8ESTACK_LO+1,x | stz P8ESTACK_HI+1,x")
else else
asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x") asmgen.out(" lda #0 | sta P8ESTACK_LO+1,x | sta P8ESTACK_HI+1,x")
@ -1795,7 +1813,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x") DataType.UBYTE -> asmgen.out(" lsr P8ESTACK_LO+1,x")
DataType.BYTE -> asmgen.out(" asl P8ESTACK_LO+1,x | ror 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.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") DataType.WORD -> asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x")
else -> throw AssemblyError("wrong dt") else -> throw AssemblyError("wrong dt")
} }
return return
@ -1870,7 +1888,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val elementDt = elementIDt.typeOrElse(DataType.STRUCT) val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar) val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
if(arrayExpr.indexer.indexNum!=null) { if(arrayExpr.indexer.indexNum!=null) {
val indexValue = arrayExpr.indexer.constIndex()!! * elementDt.memorySize() val indexValue = arrayExpr.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
when(elementDt) { when(elementDt) {
in ByteDatatypes -> { in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex") asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")

View File

@ -7,8 +7,8 @@ import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.ForLoop import prog8.ast.statements.ForLoop
import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.toHex
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -239,7 +239,7 @@ $endLabel""")
val endLabel = asmgen.makeLabel("for_end") val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel) asmgen.loopEndLabels.push(endLabel)
val iterableName = asmgen.asmVariableName(ident) val iterableName = asmgen.asmVariableName(ident)
val decl = ident.targetVarDecl(program.namespace)!! val decl = ident.targetVarDecl(program)!!
when(iterableDt) { when(iterableDt) {
DataType.STR -> { DataType.STR -> {
asmgen.out(""" asmgen.out("""
@ -369,7 +369,7 @@ $loopLabel""")
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt") throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
} }
2 -> { 2 -> {
if(range.last==255) { if(range.last==255 || range.last==254) {
asmgen.out(""" asmgen.out("""
inc $varname inc $varname
beq $endLabel beq $endLabel
@ -377,12 +377,11 @@ $loopLabel""")
bne $loopLabel""") bne $loopLabel""")
} else { } else {
asmgen.out(""" asmgen.out("""
inc $varname
inc $varname
lda $varname lda $varname
cmp #${range.last} cmp #${range.last+2}
beq $endLabel bne $loopLabel""")
inc $varname
inc $varname
jmp $loopLabel""")
} }
} }
-2 -> { -2 -> {
@ -399,12 +398,11 @@ $loopLabel""")
dec $varname dec $varname
bne $loopLabel""") bne $loopLabel""")
else -> asmgen.out(""" else -> asmgen.out("""
dec $varname
dec $varname
lda $varname lda $varname
cmp #${range.last} cmp #${range.last-2}
beq $endLabel bne $loopLabel""")
dec $varname
dec $varname
jmp $loopLabel""")
} }
} }
else -> { else -> {
@ -480,11 +478,10 @@ $loopLabel""")
$endLabel""") $endLabel""")
} else { } else {
asmgen.out(""" asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
inc $varname inc $varname
jmp $loopLabel lda $varname
cmp #${range.last+1}
bne $loopLabel
$endLabel""") $endLabel""")
} }
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
@ -512,16 +509,15 @@ $endLabel""")
1 -> { 1 -> {
asmgen.out(""" asmgen.out("""
dec $varname dec $varname
jmp $loopLabel bne $loopLabel
$endLabel""") $endLabel""")
} }
else -> { else -> {
asmgen.out(""" asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
dec $varname dec $varname
jmp $loopLabel lda $varname
cmp #${range.last-1}
bne $loopLabel
$endLabel""") $endLabel""")
} }
} }
@ -546,7 +542,6 @@ $loopLabel""")
bne + bne +
lda $varname+1 lda $varname+1
cmp #>${range.last} cmp #>${range.last}
bne +
beq $endLabel beq $endLabel
+ inc $varname + inc $varname
bne $loopLabel bne $loopLabel
@ -574,7 +569,6 @@ $loopLabel""")
bne + bne +
lda $varname+1 lda $varname+1
cmp #>${range.last} cmp #>${range.last}
bne +
beq $endLabel beq $endLabel
+ lda $varname + lda $varname
bne + bne +

View File

@ -7,9 +7,11 @@ import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.assignment.* import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -22,7 +24,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} }
internal fun saveXbeforeCall(stmt: IFunctionCall) { internal fun saveXbeforeCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}") val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) { if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA() val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
@ -34,7 +36,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} }
internal fun restoreXafterCall(stmt: IFunctionCall) { internal fun restoreXafterCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}") val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) { if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA() val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
@ -52,9 +54,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!! // NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this) // (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}") val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val subName = asmgen.asmSymbolName(stmt.target) val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) { if(stmt.args.isNotEmpty()) {
if(sub.asmParameterRegisters.isEmpty()) { if(sub.asmParameterRegisters.isEmpty()) {
// via variables // via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) { for(arg in sub.parameters.withIndex().zip(stmt.args)) {
@ -66,18 +69,37 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// just a single parameter, no risk of clobbering registers // just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0]) argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
} else { } else {
fun isNoClobberRisk(expr: Expression): Boolean {
if(expr is AddressOf ||
expr is NumericLiteralValue ||
expr is StringLiteralValue ||
expr is ArrayLiteralValue ||
expr is IdentifierReference)
return true
if(expr is FunctionCall) {
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
return isNoClobberRisk(expr.args[0])
if(expr.target.nameInSource==listOf("mkword"))
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
}
return false
}
when { when {
stmt.args.all {it is AddressOf || stmt.args.all {isNoClobberRisk(it)} -> {
it is NumericLiteralValue ||
it is StringLiteralValue ||
it is ArrayLiteralValue ||
it is IdentifierReference} -> {
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values. // There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters) val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
val (vregsArgsInfo, otherRegsArgsInfo) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters } val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
for(arg in vregsArgsInfo) val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
for(arg in cx16virtualRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second) argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in otherRegsArgsInfo) for(arg in cpuRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in statusRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second) argumentViaRegister(sub, arg.first.first, arg.first.second)
} }
else -> { else -> {
@ -150,7 +172,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
lda P8ESTACK_LO$plusIdxStr,x lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()} sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
""") """)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1") asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
else else
asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1") asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
@ -240,8 +262,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(valueDt largerThan requiredDt) if(valueDt largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types") throw AssemblyError("can only convert byte values to word param types")
} }
when { if (statusflag!=null) {
statusflag!=null -> {
if(requiredDt!=valueDt) if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required") throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) { if (statusflag == Statusflag.Pc) {
@ -260,9 +281,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
beq + beq +
sec sec
bcs ++ bcs ++
+ clc + clc
+ pla + pla
""") """)
} }
else -> { else -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A) asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
@ -270,14 +291,13 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
beq + beq +
sec sec
bcs ++ bcs ++
+ clc + clc
+""") +""")
} }
} }
} else throw AssemblyError("can only use Carry as status flag parameter")
} }
else throw AssemblyError("can only use Carry as status flag parameter") else {
}
else -> {
// via register or register pair // via register or register pair
register!! register!!
if(requiredDt largerThan valueDt) { if(requiredDt largerThan valueDt) {
@ -286,8 +306,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub) asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
asmgen.signExtendVariableLsb(scratchVar, valueDt) asmgen.signExtendVariableLsb(scratchVar, valueDt)
asmgen.assignVariableToRegister(scratchVar, register) asmgen.assignVariableToRegister(scratchVar, register)
} } else {
else {
val target: AsmAssignTarget = val target: AsmAssignTarget =
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters)) if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register) AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
@ -303,8 +322,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
} else { } else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target) AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
} }
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY)) asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY))
}
} }
} }
} }

View File

@ -5,8 +5,8 @@ import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.PostIncrDecr import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.toHex
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -67,7 +67,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar) val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT) val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
if(targetArrayIdx.indexer.indexNum!=null) { if(targetArrayIdx.indexer.indexNum!=null) {
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize() val indexValue = targetArrayIdx.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
when(elementDt) { when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue") in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> { in WordDatatypes -> {

View File

@ -1,5 +1,6 @@
package prog8.compiler.target.c64.codegen.assignment package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.IMemSizer
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
@ -104,7 +105,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
private val variableAsmName: String? = null, private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null, val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryRead? = null, val memory: DirectMemoryRead? = null,
val register: CpuRegister? = null, val register: RegisterOrPair? = null,
val number: NumericLiteralValue? = null, val number: NumericLiteralValue? = null,
val expression: Expression? = null val expression: Expression? = null
) )
@ -138,7 +139,15 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation") is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> { is IdentifierReference -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT) val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = asmgen.asmVariableName(value)) val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
if(dt==DataType.UWORD && varName.toLowerCase().startsWith("cx16.r")) {
val regStr = varName.toLowerCase().substring(5)
val reg = RegisterOrPair.valueOf(regStr.toUpperCase())
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
} else {
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
}
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value) AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
@ -148,7 +157,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value) AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
} }
is FunctionCall -> { is FunctionCall -> {
when (val sub = value.target.targetStatement(program.namespace)) { when (val sub = value.target.targetStatement(program)) {
is Subroutine -> { is Subroutine -> {
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
?: throw AssemblyError("can't translate zero return values in assignment") ?: throw AssemblyError("can't translate zero return values in assignment")
@ -198,12 +207,13 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
internal class AsmAssignment(val source: AsmAssignSource, internal class AsmAssignment(val source: AsmAssignSource,
val target: AsmAssignTarget, val target: AsmAssignTarget,
val isAugmentable: Boolean, val isAugmentable: Boolean,
memsizer: IMemSizer,
val position: Position) { val position: Position) {
init { init {
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY)) if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" } require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
require(source.datatype.memorySize() <= target.datatype.memorySize()) { require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
"source storage size must be less or equal to target datatype storage size" "source storage size must be less or equal to target datatype storage size"
} }
} }

View File

@ -4,17 +4,17 @@ import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.CpuType import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.AsmGen import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
import prog8.compiler.toHex
import prog8.functions.BuiltinFunctions
import prog8.functions.builtinFunctionReturnType
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen, private val exprAsmgen: ExpressionsAsmGen) { internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen,
private val exprAsmgen: ExpressionsAsmGen) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen) private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen)
@ -22,7 +22,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen) val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
val source = AsmAssignSource.fromAstSource(assignment.value, program, asmgen).adjustSignedUnsigned(target) val source = AsmAssignSource.fromAstSource(assignment.value, program, asmgen).adjustSignedUnsigned(target)
val assign = AsmAssignment(source, target, assignment.isAugmentable, assignment.position) val assign = AsmAssignment(source, target, assignment.isAugmentable, program.memsizer, assignment.position)
target.origAssign = assign target.origAssign = assign
if(assign.isAugmentable) if(assign.isAugmentable)
@ -66,7 +66,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val arrayVarName = asmgen.asmVariableName(value.arrayvar) val arrayVarName = asmgen.asmVariableName(value.arrayvar)
if (value.indexer.indexNum!=null) { if (value.indexer.indexNum!=null) {
// constant array index value // constant array index value
val indexValue = value.indexer.constIndex()!! * elementDt.memorySize() val indexValue = value.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
when (elementDt) { when (elementDt) {
in ByteDatatypes -> { in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue") asmgen.out(" lda $arrayVarName+$indexValue")
@ -112,6 +112,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
SourceStorageKind.MEMORY -> { SourceStorageKind.MEMORY -> {
fun assignViaExprEval(expression: Expression) {
assignExpressionToVariable(expression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
assignRegisterByte(assign.target, CpuRegister.A)
}
val value = assign.source.memory!! val value = assign.source.memory!!
when (value.addressExpression) { when (value.addressExpression) {
is NumericLiteralValue -> { is NumericLiteralValue -> {
@ -121,17 +130,20 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is IdentifierReference -> { is IdentifierReference -> {
assignMemoryByte(assign.target, null, value.addressExpression as IdentifierReference) assignMemoryByte(assign.target, null, value.addressExpression as IdentifierReference)
} }
else -> { is BinaryExpression -> {
assignExpressionToVariable(value.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope) if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
assignRegisterByte(assign.target, CpuRegister.A) assignRegisterByte(assign.target, CpuRegister.A)
} else {
assignViaExprEval(value.addressExpression)
} }
} }
else -> assignViaExprEval(value.addressExpression)
}
} }
SourceStorageKind.EXPRESSION -> { SourceStorageKind.EXPRESSION -> {
when(val value = assign.source.expression!!) { when(val value = assign.source.expression!!) {
is AddressOf -> { is AddressOf -> {
val sourceName = value.identifier.firstStructVarName(program.namespace) ?: asmgen.asmVariableName(value.identifier) val sourceName = value.identifier.firstStructVarName(program) ?: asmgen.asmVariableName(value.identifier)
assignAddressOf(assign.target, sourceName) assignAddressOf(assign.target, sourceName)
} }
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber") is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
@ -140,7 +152,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory") is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value) is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
is FunctionCall -> { is FunctionCall -> {
when (val sub = value.target.targetStatement(program.namespace)) { when (val sub = value.target.targetStatement(program)) {
is Subroutine -> { is Subroutine -> {
asmgen.saveXbeforeCall(value) asmgen.saveXbeforeCall(value)
asmgen.translateFunctionCall(value) asmgen.translateFunctionCall(value)
@ -197,7 +209,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
is BuiltinFunctionStatementPlaceholder -> { is BuiltinFunctionStatementPlaceholder -> {
val signature = BuiltinFunctions.getValue(sub.name) val signature = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(value, signature, false) asmgen.translateBuiltinFunctionCallExpression(value, signature, false, assign.target.register)
if(assign.target.register==null) {
// still need to assign the result to the target variable/etc.
val returntype = builtinFunctionReturnType(sub.name, value.args, program) val returntype = builtinFunctionReturnType(sub.name, value.args, program)
if(!returntype.isKnown) if(!returntype.isKnown)
throw AssemblyError("unknown dt") throw AssemblyError("unknown dt")
@ -227,6 +241,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
else -> throw AssemblyError("weird result type") else -> throw AssemblyError("weird result type")
} }
} }
}
else -> { else -> {
throw AssemblyError("weird func call") throw AssemblyError("weird func call")
} }
@ -248,7 +263,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
SourceStorageKind.REGISTER -> { SourceStorageKind.REGISTER -> {
assignRegisterByte(assign.target, assign.source.register!!) when(assign.source.datatype) {
DataType.UBYTE -> assignRegisterByte(assign.target, assign.source.register!!.asCpuRegister())
DataType.UWORD -> assignRegisterpairWord(assign.target, assign.source.register!!)
else -> throw AssemblyError("invalid register dt")
}
} }
SourceStorageKind.STACK -> { SourceStorageKind.STACK -> {
assignStackValue(assign.target) assignStackValue(assign.target)
@ -297,15 +316,38 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
if(targetDt in WordDatatypes) { if(targetDt in WordDatatypes) {
if (value.addressExpression is NumericLiteralValue) {
fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
assignRegisterByte(target, CpuRegister.A)
}
when (value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt() val address = (value.addressExpression as NumericLiteralValue).number.toInt()
assignMemoryByteIntoWord(target, address, null) assignMemoryByteIntoWord(target, address, null)
return return
} }
else if (value.addressExpression is IdentifierReference) { is IdentifierReference -> {
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference) assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
return return
} }
is BinaryExpression -> {
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
asmgen.out(" ldy #0")
assignRegisterpairWord(target, RegisterOrPair.AY)
} else {
assignViaExprEval(value.addressExpression)
}
}
else -> {
assignViaExprEval(value.addressExpression)
}
}
} }
} }
is NumericLiteralValue -> throw AssemblyError("a cast of a literal value should have been const-folded away") is NumericLiteralValue -> throw AssemblyError("a cast of a literal value should have been const-folded away")
@ -399,7 +441,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position) val lsb = FunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
lsb.linkParents(value.parent) lsb.linkParents(value.parent)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb) val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
val assign = AsmAssignment(src, target, false, value.position) val assign = AsmAssignment(src, target, false, program.memsizer, value.position)
translateNormalAssignment(assign) translateNormalAssignment(assign)
} }
@ -431,7 +473,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName") asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
} }
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1") asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
else else
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1") asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@ -454,7 +496,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName") asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
} }
DataType.UWORD -> { DataType.UWORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1") asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
else else
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1") asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@ -550,7 +592,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName") asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
} }
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1") asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
else else
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1") asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@ -578,7 +620,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName") asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
} }
DataType.UWORD -> { DataType.UWORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1") asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
else else
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1") asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@ -715,12 +757,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
asmgen.out(" inx") asmgen.out(" inx | lda P8ESTACK_LO,x")
storeByteViaRegisterAInMemoryAddress("P8ESTACK_LO,x", target.memory!!) storeRegisterAInMemoryAddress(target.memory!!)
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
if(target.constArrayIndexValue!=null) { if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize() val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
when(target.datatype) { when(target.datatype) {
in ByteDatatypes -> { in ByteDatatypes -> {
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx") asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
@ -926,7 +968,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
target.array!! target.array!!
if(target.constArrayIndexValue!=null) { if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize() val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
when(target.datatype) { when(target.datatype) {
in ByteDatatypes -> { in ByteDatatypes -> {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx") asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
@ -1144,11 +1186,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""") """)
} }
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!) asmgen.out(" lda $sourceName")
storeRegisterAInMemoryAddress(target.memory!!)
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) { if (target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize() val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx") asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
} }
else { else {
@ -1252,7 +1295,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(wordtarget.kind) { when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}") asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+1") asmgen.out(" stz ${wordtarget.asmVarname}+1")
else else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1") asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
@ -1261,7 +1304,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
if (wordtarget.constArrayIndexValue!=null) { if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2 val scaledIdx = wordtarget.constArrayIndexValue!! * 2
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx") asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1") asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
else else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1") asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1")
@ -1286,7 +1329,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x") asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI,x | dex") asmgen.out(" stz P8ESTACK_HI,x | dex")
else else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex") asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
@ -1304,7 +1347,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" st${register.name.toLowerCase()} ${target.asmVarname}") asmgen.out(" st${register.name.toLowerCase()} ${target.asmVarname}")
} }
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
storeRegisterInMemoryAddress(register, target.memory!!) when(register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
storeRegisterAInMemoryAddress(target.memory!!)
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) { if (target.constArrayIndexValue!=null) {
@ -1390,7 +1438,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname} | stx ${target.asmVarname}+1") RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname} | stx ${target.asmVarname}+1")
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname} | sty ${target.asmVarname}+1") RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname} | sty ${target.asmVarname}+1")
RegisterOrPair.XY -> asmgen.out(" stx ${target.asmVarname} | sty ${target.asmVarname}+1") RegisterOrPair.XY -> asmgen.out(" stx ${target.asmVarname} | sty ${target.asmVarname}+1")
else -> throw AssemblyError("expected reg pair") in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
asmgen.out("""
lda $srcReg
sta ${target.asmVarname}
lda $srcReg+1
sta ${target.asmVarname}+1""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
} }
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
@ -1400,11 +1456,20 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname}+$idx | stx ${target.asmVarname}+$idx+1") RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname}+$idx | stx ${target.asmVarname}+$idx+1")
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1") RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1")
RegisterOrPair.XY -> asmgen.out(" stx ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1") RegisterOrPair.XY -> asmgen.out(" stx ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1")
else -> throw AssemblyError("expected reg pair") in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
asmgen.out("""
lda $srcReg
sta ${target.asmVarname}+$idx
lda $srcReg+1
sta ${target.asmVarname}+$idx+1""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
} }
} }
else { else {
when(regs) { if (regs !in Cx16VirtualRegisters) {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha") RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha") RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha") RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
@ -1417,6 +1482,16 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
dey dey
pla pla
sta ${target.asmVarname},y""") sta ${target.asmVarname},y""")
} else {
val srcReg = asmgen.asmSymbolName(regs)
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y, true)
asmgen.out("""
lda $srcReg+1
sta ${target.asmVarname},y
dey
lda $srcReg
sta ${target.asmVarname},y""")
}
} }
} }
TargetStorageKind.REGISTER -> { TargetStorageKind.REGISTER -> {
@ -1431,7 +1506,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
stx cx16.${target.register.toString().toLowerCase()}+1 stx cx16.${target.register.toString().toLowerCase()}+1
""") """)
} }
else -> throw AssemblyError("expected reg pair") else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
} }
RegisterOrPair.AY -> when(target.register!!) { RegisterOrPair.AY -> when(target.register!!) {
RegisterOrPair.AY -> { } RegisterOrPair.AY -> { }
@ -1443,7 +1518,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sty cx16.${target.register.toString().toLowerCase()}+1 sty cx16.${target.register.toString().toLowerCase()}+1
""") """)
} }
else -> throw AssemblyError("expected reg pair") else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
} }
RegisterOrPair.XY -> when(target.register!!) { RegisterOrPair.XY -> when(target.register!!) {
RegisterOrPair.AY -> { asmgen.out(" txa") } RegisterOrPair.AY -> { asmgen.out(" txa") }
@ -1455,16 +1530,40 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sty cx16.${target.register.toString().toLowerCase()}+1 sty cx16.${target.register.toString().toLowerCase()}+1
""") """)
} }
else -> throw AssemblyError("expected reg pair") else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
} }
else -> throw AssemblyError("expected reg pair") in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
if(regs!=target.register) {
when(target.register) {
RegisterOrPair.AX -> asmgen.out(" lda $srcReg | ldx $srcReg+1")
RegisterOrPair.AY -> asmgen.out(" lda $srcReg | ldy $srcReg+1")
RegisterOrPair.XY -> asmgen.out(" ldx $srcReg | ldy $srcReg+1")
in Cx16VirtualRegisters -> {
val targetReg = asmgen.asmSymbolName(target.register!!)
asmgen.out(" lda $srcReg | sta $targetReg | lda $srcReg+1 | sta $targetReg+1")
}
else -> throw AssemblyError("invalid reg")
}
}
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
} }
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
when(regs) { when(regs) {
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex") RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex")
RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here") RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here")
else -> throw AssemblyError("expected reg pair") in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
asmgen.out("""
lda $srcReg
sta P8ESTACK_LO,x
lda $srcReg+1
sta P8ESTACK_HI,x
dex""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
} }
} }
TargetStorageKind.MEMORY -> throw AssemblyError("can't store word into memory byte") TargetStorageKind.MEMORY -> throw AssemblyError("can't store word into memory byte")
@ -1472,7 +1571,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
private fun assignConstantWord(target: AsmAssignTarget, word: Int) { private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
if(word==0 && CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) { if(word==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
// optimize setting zero value for this processor // optimize setting zero value for this processor
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
@ -1567,14 +1666,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) { private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
if(byte==0.toShort() && CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) { if(byte==0.toShort() && asmgen.isTargetCpu(CpuType.CPU65c02)) {
// optimize setting zero value for this cpu // optimize setting zero value for this cpu
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
asmgen.out(" stz ${target.asmVarname} ") asmgen.out(" stz ${target.asmVarname} ")
} }
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
storeByteViaRegisterAInMemoryAddress("#${byte.toHex()}", target.memory!!) asmgen.out(" lda #${byte.toHex()}")
storeRegisterAInMemoryAddress(target.memory!!)
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) { if (target.constArrayIndexValue!=null) {
@ -1613,7 +1713,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname} ") asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname} ")
} }
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
storeByteViaRegisterAInMemoryAddress("#${byte.toHex()}", target.memory!!) asmgen.out(" lda #${byte.toHex()}")
storeRegisterAInMemoryAddress(target.memory!!)
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) { if (target.constArrayIndexValue!=null) {
@ -1635,7 +1736,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float") RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> { in Cx16VirtualRegisters -> {
asmgen.out(" lda #${byte.toHex()} | sta cx16.${target.register.toString().toLowerCase()}") asmgen.out(" lda #${byte.toHex()} | sta cx16.${target.register.toString().toLowerCase()}")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()}+1\n") asmgen.out(" stz cx16.${target.register.toString().toLowerCase()}+1\n")
else else
asmgen.out(" lda #0 | sta cx16.${target.register.toString().toLowerCase()}+1\n") asmgen.out(" lda #0 | sta cx16.${target.register.toString().toLowerCase()}+1\n")
@ -1656,7 +1757,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// optimized case for float zero // optimized case for float zero
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(""" asmgen.out("""
stz ${target.asmVarname} stz ${target.asmVarname}
stz ${target.asmVarname}+1 stz ${target.asmVarname}+1
@ -1676,8 +1777,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
if (target.array!!.indexer.indexNum!=null) { if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * DataType.FLOAT.memorySize() val indexValue = target.array.indexer.constIndex()!! * program.memsizer.memorySize(DataType.FLOAT)
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(""" asmgen.out("""
stz ${target.asmVarname}+$indexValue stz ${target.asmVarname}+$indexValue
stz ${target.asmVarname}+$indexValue+1 stz ${target.asmVarname}+$indexValue+1
@ -1741,7 +1842,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
val arrayVarName = target.asmVarname val arrayVarName = target.asmVarname
if (target.array!!.indexer.indexNum!=null) { if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * DataType.FLOAT.memorySize() val indexValue = target.array.indexer.constIndex()!! * program.memsizer.memorySize(DataType.FLOAT)
asmgen.out(""" asmgen.out("""
lda $constFloat lda $constFloat
sta $arrayVarName+$indexValue sta $arrayVarName+$indexValue
@ -1797,7 +1898,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""") """)
} }
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
storeByteViaRegisterAInMemoryAddress(address.toHex(), target.memory!!) asmgen.out(" lda ${address.toHex()}")
storeRegisterAInMemoryAddress(target.memory!!)
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
throw AssemblyError("no asm gen for assign memory byte at $address to array ${target.asmVarname}") throw AssemblyError("no asm gen for assign memory byte at $address to array ${target.asmVarname}")
@ -1834,8 +1936,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" sta ${target.asmVarname}") asmgen.out(" sta ${target.asmVarname}")
} }
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
val sourceName = asmgen.asmVariableName(identifier) asmgen.loadByteFromPointerIntoA(identifier)
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!) storeRegisterAInMemoryAddress(target.memory!!)
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
throw AssemblyError("no asm gen for assign memory byte $identifier to array ${target.asmVarname} ") throw AssemblyError("no asm gen for assign memory byte $identifier to array ${target.asmVarname} ")
@ -1873,7 +1975,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(wordtarget.kind) { when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
asmgen.out(" lda ${address.toHex()} | sta ${wordtarget.asmVarname}") asmgen.out(" lda ${address.toHex()} | sta ${wordtarget.asmVarname}")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+1") asmgen.out(" stz ${wordtarget.asmVarname}+1")
else else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1") asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
@ -1889,7 +1991,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
asmgen.out(" lda ${address.toHex()} | sta P8ESTACK_LO,x") asmgen.out(" lda ${address.toHex()} | sta P8ESTACK_LO,x")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI,x | dex") asmgen.out(" stz P8ESTACK_HI,x | dex")
else else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex") asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
@ -1901,7 +2003,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
asmgen.loadByteFromPointerIntoA(identifier) asmgen.loadByteFromPointerIntoA(identifier)
asmgen.out(" sta ${wordtarget.asmVarname}") asmgen.out(" sta ${wordtarget.asmVarname}")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+1") asmgen.out(" stz ${wordtarget.asmVarname}+1")
else else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1") asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
@ -1921,7 +2023,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
asmgen.loadByteFromPointerIntoA(identifier) asmgen.loadByteFromPointerIntoA(identifier)
asmgen.out(" sta P8ESTACK_LO,x") asmgen.out(" sta P8ESTACK_LO,x")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI,x | dex") asmgen.out(" stz P8ESTACK_HI,x | dex")
else else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex") asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
@ -1931,67 +2033,97 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
private fun storeByteViaRegisterAInMemoryAddress(ldaInstructionArg: String, memoryAddress: DirectMemoryWrite) { private fun storeRegisterAInMemoryAddress(memoryAddress: DirectMemoryWrite) {
val addressExpr = memoryAddress.addressExpression val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue val addressLv = addressExpr as? NumericLiteralValue
when {
addressLv != null -> { fun storeViaExprEval() {
asmgen.out(" lda $ldaInstructionArg | sta ${addressLv.number.toHex()}") when(addressExpr) {
} is NumericLiteralValue, is IdentifierReference -> {
addressExpr is IdentifierReference -> { assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.storeByteIntoPointer(addressExpr, ldaInstructionArg) if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
} }
else -> { else -> {
// same as above but we need to save the A register
asmgen.out(" pha")
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null) assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.out(" ldy #0 | lda $ldaInstructionArg | sta (P8ZP_SCRATCH_W2),y") asmgen.out(" pla")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
}
}
}
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
val sourceName = asmgen.asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
asmgen.out(" sta ($sourceName)")
} else {
asmgen.out("""
ldy $sourceName
sty P8ZP_SCRATCH_W2
ldy $sourceName+1
sty P8ZP_SCRATCH_W2+1
sta (P8ZP_SCRATCH_W2)""")
}
} else {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
asmgen.out(" ldy #0 | sta ($sourceName),y")
} else {
asmgen.out("""
ldy $sourceName
sty P8ZP_SCRATCH_W2
ldy $sourceName+1
sty P8ZP_SCRATCH_W2+1
ldy #0
sta (P8ZP_SCRATCH_W2),y""")
} }
} }
} }
private fun storeRegisterInMemoryAddress(register: CpuRegister, memoryAddress: DirectMemoryWrite) {
// this is optimized for register A.
val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
val registerName = register.name.toLowerCase()
when { when {
addressLv != null -> { addressLv != null -> {
asmgen.out(" st$registerName ${addressLv.number.toHex()}") asmgen.out(" sta ${addressLv.number.toHex()}")
} }
addressExpr is IdentifierReference -> { addressExpr is IdentifierReference -> {
when (register) { storeAIntoPointerVar(addressExpr)
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
} }
asmgen.storeByteIntoPointer(addressExpr, null) addressExpr is BinaryExpression -> {
} if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, true))
else -> { storeViaExprEval()
asmgen.saveRegisterStack(register, false)
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.restoreRegisterStack(CpuRegister.A, false)
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
} }
else -> storeViaExprEval()
} }
} }
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) { internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen) val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen) val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, expr.position) val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign) translateNormalAssignment(assign)
} }
internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) { internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen) val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, scope, variableAsmName = asmVarName) val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, scope, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, expr.position) val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign) translateNormalAssignment(assign)
} }
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) { internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) {
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen) val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName) val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, Position.DUMMY) val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
translateNormalAssignment(assign) translateNormalAssignment(assign)
} }
} }

View File

@ -4,13 +4,11 @@ import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType import prog8.compiler.target.CpuType
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.codegen.AsmGen import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
import prog8.compiler.toHex
internal class AugmentableAssignmentAsmGen(private val program: Program, internal class AugmentableAssignmentAsmGen(private val program: Program,
private val assignmentAsmGen: AssignmentAsmGen, private val assignmentAsmGen: AssignmentAsmGen,
@ -202,7 +200,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
with(target.array!!.indexer) { with(target.array!!.indexer) {
when { when {
indexNum!=null -> { indexNum!=null -> {
val targetVarName = "${target.asmVarname} + ${indexNum!!.number.toInt()*target.datatype.memorySize()}" val targetVarName = "${target.asmVarname} + ${indexNum!!.number.toInt()*program.memsizer.memorySize(target.datatype)}"
when(target.datatype) { when(target.datatype) {
in ByteDatatypes -> { in ByteDatatypes -> {
when { when {
@ -246,19 +244,19 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when(target.datatype) { when(target.datatype) {
in ByteDatatypes -> { in ByteDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen) val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen)
val assign = AsmAssignment(target.origAssign.source, tgt, false, value.position) val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign) assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A) assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
} }
in WordDatatypes -> { in WordDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen) val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen)
val assign = AsmAssignment(target.origAssign.source, tgt, false, value.position) val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign) assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY) assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.FAC1, null, program, asmgen) val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.FAC1, null, program, asmgen)
val assign = AsmAssignment(target.origAssign.source, tgt, false, value.position) val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign) assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignFAC1float(target) assignmentAsmGen.assignFAC1float(target)
} }
@ -632,7 +630,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
"<<" -> { "<<" -> {
if(value>=8) { if(value>=8) {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name") asmgen.out(" stz $name")
else else
asmgen.out(" lda #0 | sta $name") asmgen.out(" lda #0 | sta $name")
@ -643,7 +641,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
if(value>0) { if(value>0) {
if (dt == DataType.UBYTE) { if (dt == DataType.UBYTE) {
if(value>=8) { if(value>=8) {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name") asmgen.out(" stz $name")
else else
asmgen.out(" lda #0 | sta $name") asmgen.out(" lda #0 | sta $name")
@ -858,14 +856,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"<<" -> { "<<" -> {
when { when {
value>=16 -> { value>=16 -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name | stz $name+1") asmgen.out(" stz $name | stz $name+1")
else else
asmgen.out(" lda #0 | sta $name | sta $name+1") asmgen.out(" lda #0 | sta $name | sta $name+1")
} }
value==8 -> { value==8 -> {
asmgen.out(" lda $name | sta $name+1") asmgen.out(" lda $name | sta $name+1")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name") asmgen.out(" stz $name")
else else
asmgen.out(" lda #0 | sta $name") asmgen.out(" lda #0 | sta $name")
@ -885,14 +883,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
if(dt==DataType.UWORD) { if(dt==DataType.UWORD) {
when { when {
value>=16 -> { value>=16 -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name | stz $name+1") asmgen.out(" stz $name | stz $name+1")
else else
asmgen.out(" lda #0 | sta $name | sta $name+1") asmgen.out(" lda #0 | sta $name | sta $name+1")
} }
value==8 -> { value==8 -> {
asmgen.out(" lda $name+1 | sta $name") asmgen.out(" lda $name+1 | sta $name")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1") asmgen.out(" stz $name+1")
else else
asmgen.out(" lda #0 | sta $name+1") asmgen.out(" lda #0 | sta $name+1")
@ -941,13 +939,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> { "&", "and" -> {
when { when {
value == 0 -> { value == 0 -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name | stz $name+1") asmgen.out(" stz $name | stz $name+1")
else else
asmgen.out(" lda #0 | sta $name | sta $name+1") asmgen.out(" lda #0 | sta $name | sta $name+1")
} }
value and 255 == 0 -> { value and 255 == 0 -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name") asmgen.out(" stz $name")
else else
asmgen.out(" lda #0 | sta $name") asmgen.out(" lda #0 | sta $name")
@ -955,7 +953,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
value < 0x0100 -> { value < 0x0100 -> {
asmgen.out(" lda $name | and #$value | sta $name") asmgen.out(" lda $name | and #$value | sta $name")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1") asmgen.out(" stz $name+1")
else else
asmgen.out(" lda #0 | sta $name+1") asmgen.out(" lda #0 | sta $name+1")
@ -986,7 +984,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) { private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) {
val otherName = asmgen.asmVariableName(ident) val otherName = asmgen.asmVariableName(ident)
val valueDt = ident.targetVarDecl(program.namespace)!!.datatype val valueDt = ident.targetVarDecl(program)!!.datatype
when (valueDt) { when (valueDt) {
in ByteDatatypes -> { in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that // the other variable is a BYTE type so optimize for that
@ -1042,7 +1040,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
"*" -> { "*" -> {
asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1") asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ZP_SCRATCH_W1+1") asmgen.out(" stz P8ZP_SCRATCH_W1+1")
else else
asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1") asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1")
@ -1093,7 +1091,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> { "&", "and" -> {
asmgen.out(" lda $otherName | and $name | sta $name") asmgen.out(" lda $otherName | and $name | sta $name")
if(dt in WordDatatypes) { if(dt in WordDatatypes) {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1") asmgen.out(" stz $name+1")
else else
asmgen.out(" lda #0 | sta $name+1") asmgen.out(" lda #0 | sta $name+1")
@ -1352,7 +1350,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(value, RegisterOrPair.A) asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out(" and $name | sta $name") asmgen.out(" and $name | sta $name")
if(dt in WordDatatypes) { if(dt in WordDatatypes) {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1") asmgen.out(" stz $name+1")
else else
asmgen.out(" lda #0 | sta $name+1") asmgen.out(" lda #0 | sta $name+1")
@ -1467,7 +1465,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference, scope: Subroutine) { private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference, scope: Subroutine) {
val valueDt = ident.targetVarDecl(program.namespace)!!.datatype val valueDt = ident.targetVarDecl(program)!!.datatype
if(valueDt != DataType.FLOAT) if(valueDt != DataType.FLOAT)
throw AssemblyError("float variable expected") throw AssemblyError("float variable expected")
@ -1475,7 +1473,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.saveRegisterLocal(CpuRegister.X, scope) asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) { when (operator) {
"**" -> { "**" -> {
if(CompilationTarget.instance is Cx16Target) { if(asmgen.haveFPWR()) {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$otherName
ldy #>$otherName
jsr floats.FPWR
""")
} else
// cx16 doesn't have FPWR() only FPWRT() // cx16 doesn't have FPWR() only FPWRT()
asmgen.out(""" asmgen.out("""
lda #<$name lda #<$name
@ -1486,15 +1493,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
jsr floats.MOVFM jsr floats.MOVFM
jsr floats.FPWRT jsr floats.FPWRT
""") """)
} else
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$otherName
ldy #>$otherName
jsr floats.FPWR
""")
} }
"+" -> { "+" -> {
asmgen.out(""" asmgen.out("""
@ -1553,7 +1551,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.saveRegisterLocal(CpuRegister.X, scope) asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) { when (operator) {
"**" -> { "**" -> {
if(CompilationTarget.instance is Cx16Target) { if(asmgen.haveFPWR()) {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$constValueName
ldy #>$constValueName
jsr floats.FPWR
""")
} else
// cx16 doesn't have FPWR() only FPWRT() // cx16 doesn't have FPWR() only FPWRT()
asmgen.out(""" asmgen.out("""
lda #<$name lda #<$name
@ -1564,15 +1571,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
jsr floats.MOVFM jsr floats.MOVFM
jsr floats.FPWRT jsr floats.FPWRT
""") """)
} else
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$constValueName
ldy #>$constValueName
jsr floats.FPWR
""")
} }
"+" -> { "+" -> {
if (value == 0.0) if (value == 0.0)
@ -1645,7 +1643,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${target.asmVarname}+1") asmgen.out(" stz ${target.asmVarname}+1")
else else
asmgen.out(" lda #0 | sta ${target.asmVarname}+1") asmgen.out(" lda #0 | sta ${target.asmVarname}+1")
@ -1655,7 +1653,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" lda #0 | sta ${target.asmVarname},y") asmgen.out(" lda #0 | sta ${target.asmVarname},y")
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI+1,x") asmgen.out(" stz P8ESTACK_HI+1,x")
else else
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x") asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")

View File

@ -1,11 +1,9 @@
package prog8.compiler.target.cx16 package prog8.compiler.target.cx16
import prog8.ast.Program
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.CpuType import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition import prog8.compiler.target.c64.C64MachineDefinition
import prog8.parser.ModuleImporter
import java.io.IOException import java.io.IOException
internal object CX16MachineDefinition: IMachineDefinition { internal object CX16MachineDefinition: IMachineDefinition {
@ -27,10 +25,11 @@ internal object CX16MachineDefinition: IMachineDefinition {
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num) override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) { return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) listOf("syslib")
importer.importLibraryModule(program, "syslib") else
emptyList()
} }
override fun launchEmulator(programName: String) { override fun launchEmulator(programName: String) {

View File

@ -1,16 +1,16 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
internal class BinExprSplitter(private val program: Program) : AstWalker() { internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { // override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@ -53,7 +53,7 @@ X = BinExpr X = LeftExpr
*/ */
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) { if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program)) {
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right) if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
return noModifications return noModifications
@ -78,9 +78,9 @@ X = BinExpr X = LeftExpr
private fun isSimpleExpression(expr: Expression) = private fun isSimpleExpression(expr: Expression) =
expr is IdentifierReference || expr is NumericLiteralValue || expr is AddressOf || expr is DirectMemoryRead || expr is StringLiteralValue || expr is ArrayLiteralValue || expr is RangeExpr expr is IdentifierReference || expr is NumericLiteralValue || expr is AddressOf || expr is DirectMemoryRead || expr is StringLiteralValue || expr is ArrayLiteralValue || expr is RangeExpr
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope) = private fun isSimpleTarget(target: AssignTarget, program: Program) =
if (target.identifier!=null || target.memoryAddress!=null) if (target.identifier!=null || target.memoryAddress!=null)
target.isInRegularRAM(namespace) compTarget.isInRegularRAM(target, program)
else else
false false

View File

@ -1,26 +1,29 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.* import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.ErrorReporter
import prog8.ast.base.ParentSentinel import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.FunctionCall import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.loadAsmIncludeFile import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import java.nio.file.Path
private val alwaysKeepSubroutines = setOf( private val alwaysKeepSubroutines = setOf(
Pair("main", "start"), Pair("main", "start")
Pair("irq", "irq")
) )
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
class CallGraph(private val program: Program) : IAstVisitor { class CallGraph(private val program: Program, private val asmFileLoader: (filename: String, source: Path)->String) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() } val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() } val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
@ -77,7 +80,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
imports[thisModule] = imports.getValue(thisModule).plus(importedModule) imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule) importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") { } else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source) val asm = asmFileLoader(directive.args[0].str!!, thisModule.source)
val scope = directive.definingSubroutine() val scope = directive.definingSubroutine()
if(scope!=null) { if(scope!=null) {
scanAssemblyCode(asm, directive, scope) scanAssemblyCode(asm, directive, scope)
@ -89,7 +92,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
override fun visit(identifier: IdentifierReference) { override fun visit(identifier: IdentifierReference) {
// track symbol usage // track symbol usage
val target = identifier.targetStatement(this.program.namespace) val target = identifier.targetStatement(program)
if (target != null) { if (target != null) {
addNodeAndParentScopes(target) addNodeAndParentScopes(target)
} }
@ -126,7 +129,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
} }
override fun visit(functionCall: FunctionCall) { override fun visit(functionCall: FunctionCall) {
val otherSub = functionCall.target.targetSubroutine(program.namespace) val otherSub = functionCall.target.targetSubroutine(program)
if (otherSub != null) { if (otherSub != null) {
functionCall.definingSubroutine()?.let { thisSub -> functionCall.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub) calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
@ -137,7 +140,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
} }
override fun visit(functionCallStatement: FunctionCallStatement) { override fun visit(functionCallStatement: FunctionCallStatement) {
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace) val otherSub = functionCallStatement.target.targetSubroutine(program)
if (otherSub != null) { if (otherSub != null) {
functionCallStatement.definingSubroutine()?.let { thisSub -> functionCallStatement.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub) calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
@ -147,8 +150,19 @@ class CallGraph(private val program: Program) : IAstVisitor {
super.visit(functionCallStatement) super.visit(functionCallStatement)
} }
override fun visit(addressOf: AddressOf) {
val otherSub = addressOf.identifier.targetSubroutine(program)
if(otherSub!=null) {
addressOf.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(thisSub)
}
}
super.visit(addressOf)
}
override fun visit(jump: Jump) { override fun visit(jump: Jump) {
val otherSub = jump.identifier?.targetSubroutine(program.namespace) val otherSub = jump.identifier?.targetSubroutine(program)
if (otherSub != null) { if (otherSub != null) {
jump.definingSubroutine()?.let { thisSub -> jump.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub) calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
@ -210,7 +224,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
} }
} }
fun checkRecursiveCalls(errors: ErrorReporter) { fun checkRecursiveCalls(errors: IErrorReporter) {
val cycles = recursionCycles() val cycles = recursionCycles()
if(cycles.any()) { if(cycles.any()) {
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY) errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)

View File

@ -4,13 +4,16 @@ import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker import prog8.ast.statements.Assignment
import prog8.ast.processing.IAstModification import prog8.ast.statements.ForLoop
import prog8.ast.statements.* import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
import kotlin.math.pow import kotlin.math.pow
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() { internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> { override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
@ -221,7 +224,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
range.step range.step
} }
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position) return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, compTarget, range.position)
} }
// adjust the datatype of a range expression in for loops to the loop variable. // adjust the datatype of a range expression in for loops to the loop variable.
@ -230,7 +233,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
val rangeTo = iterableRange.to as? NumericLiteralValue val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return noModifications if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace) ?: throw UndefinedSymbolError(forLoop.loopVar) val loopvar = forLoop.loopVar.targetVarDecl(program) ?: throw UndefinedSymbolError(forLoop.loopVar)
val stepLiteral = iterableRange.step as? NumericLiteralValue val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) { when(loopvar.datatype) {

View File

@ -4,16 +4,17 @@ import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.ArrayIndex import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.ForLoop import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.compiler.target.CompilationTarget import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
// Fix up the literal value's type to match that of the vardecl // 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, private val errors: IErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@ -38,7 +39,7 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
// Replace all constant identifiers with their actual value, // Replace all constant identifiers with their actual value,
// and the array var initializer values and sizes. // and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those. // This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() { internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> { override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
@ -191,7 +192,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr==null && litval!=null) { if(rangeExpr==null && litval!=null) {
// arraysize initializer is a single int, and we know the size. // arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble() val fillvalue = litval.number.toDouble()
if (fillvalue < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE) if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", litval.position) errors.err("float value overflow", litval.position)
else { else {
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.

View File

@ -2,11 +2,14 @@ package prog8.optimizer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.IntegerDatatypes
import prog8.ast.base.NumericDatatypes
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.pow import kotlin.math.pow

View File

@ -1,16 +1,19 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.ErrorReporter import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal fun Program.constantFold(errors: ErrorReporter) { internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors) val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this) valuetypefixer.visit(this)
if(errors.isEmpty()) { if(errors.isEmpty()) {
valuetypefixer.applyModifications() valuetypefixer.applyModifications()
val replacer = ConstantIdentifierReplacer(this, errors) val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
replacer.visit(this) replacer.visit(this)
if (errors.isEmpty()) { if (errors.isEmpty()) {
replacer.applyModifications() replacer.applyModifications()
@ -19,7 +22,7 @@ internal fun Program.constantFold(errors: ErrorReporter) {
if(errors.isEmpty()) { if(errors.isEmpty()) {
valuetypefixer.applyModifications() valuetypefixer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this) val optimizer = ConstantFoldingOptimizer(this, compTarget)
optimizer.visit(this) optimizer.visit(this)
while (errors.isEmpty() && optimizer.applyModifications() > 0) { while (errors.isEmpty() && optimizer.applyModifications() > 0) {
optimizer.visit(this) optimizer.visit(this)
@ -38,8 +41,11 @@ internal fun Program.constantFold(errors: ErrorReporter) {
} }
internal fun Program.optimizeStatements(errors: ErrorReporter): Int { internal fun Program.optimizeStatements(errors: IErrorReporter,
val optimizer = StatementOptimizer(this, errors) functions: IBuiltinFunctions,
compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget, asmFileLoader)
optimizer.visit(this) optimizer.visit(this)
val optimizationCount = optimizer.applyModifications() val optimizationCount = optimizer.applyModifications()
@ -54,8 +60,8 @@ internal fun Program.simplifyExpressions() : Int {
return opti.applyModifications() return opti.applyModifications()
} }
internal fun Program.splitBinaryExpressions() : Int { internal fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int {
val opti = BinExprSplitter(this) val opti = BinExprSplitter(this, compTarget)
opti.visit(this) opti.visit(this)
return opti.applyModifications() return opti.applyModifications()
} }

View File

@ -1,29 +1,35 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget import prog8.ast.walk.AstWalker
import prog8.functions.BuiltinFunctions import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
import kotlin.math.floor import kotlin.math.floor
internal class StatementOptimizer(private val program: Program, internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter) : AstWalker() { private val errors: IErrorReporter,
private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String
) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
private val callgraph = CallGraph(program) private val callgraph = CallGraph(program, asmFileLoader)
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
override fun after(block: Block, parent: Node): Iterable<IAstModification> { override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) { if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) { if (block.containsNoCodeNorVars()) {
if(block.name != program.internedStringsModuleName)
errors.warn("removing empty block '${block.name}'", block.position) errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope)) return listOf(IAstModification.Remove(block, parent as INameScope))
} }
@ -50,6 +56,7 @@ internal class StatementOptimizer(private val program: Program,
} }
if(subroutine !in callgraph.usedSymbols && !forceOutput) { if(subroutine !in callgraph.usedSymbols && !forceOutput) {
if(!subroutine.isAsmSubroutine)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position) errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope())) return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
} }
@ -70,60 +77,58 @@ internal class StatementOptimizer(private val program: Program,
} }
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) { if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
val functionName = functionCallStatement.target.nameInSource[0] val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) { if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position) errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope())) return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
} }
} }
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters // printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
// this is a C-64 specific optimization if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print")) {
val arg = functionCallStatement.args.single() val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference? val stringVar: IdentifierReference? = if(arg is AddressOf) {
stringVar = if(arg is AddressOf) {
arg.identifier arg.identifier
} else { } else {
arg as? IdentifierReference arg as? IdentifierReference
} }
if(stringVar!=null) { if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program.namespace)!! val vardecl = stringVar.targetVarDecl(program)!!
val string = vardecl.value as? StringLiteralValue val string = vardecl.value as? StringLiteralValue
if(string!=null) { if(string!=null) {
val pos = functionCallStatement.position val pos = functionCallStatement.position
if (string.value.length == 1) { if (string.value.length == 1) {
val firstCharEncoded = CompilationTarget.instance.encodeString(string.value, string.altEncoding)[0] val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement( val chrout = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos), IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)), mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos functionCallStatement.void, pos
) )
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent)) return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if (string.value.length == 2) { } else if (string.value.length == 2) {
val firstTwoCharsEncoded = CompilationTarget.instance.encodeString(string.value.take(2), string.altEncoding) val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement( val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos), IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)), mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos functionCallStatement.void, pos
) )
val chrout2 = FunctionCallStatement( val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos), IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)), mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos functionCallStatement.void, pos
) )
val anonscope = AnonymousScope(mutableListOf(), pos) return listOf(
anonscope.statements.add(chrout1) IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
anonscope.statements.add(chrout2) IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent)) )
} }
} }
} }
} }
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter // if the first instruction in the called subroutine is a return statement, remove the jump altogeter
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace) val subroutine = functionCallStatement.target.targetSubroutine(program)
if(subroutine!=null) { if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return) if(first is Return)
@ -135,7 +140,7 @@ internal class StatementOptimizer(private val program: Program,
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> { override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value // if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program.namespace) val subroutine = functionCall.target.targetSubroutine(program)
if(subroutine!=null) { if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value!=null) { if(first is Return && first.value!=null) {
@ -203,14 +208,14 @@ internal class StatementOptimizer(private val program: Program,
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
} }
} }
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program.namespace) val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program)
if(iterable!=null) { if(iterable!=null) {
if(iterable.datatype==DataType.STR) { if(iterable.datatype==DataType.STR) {
val sv = iterable.value as StringLiteralValue val sv = iterable.value as StringLiteralValue
val size = sv.value.length val size = sv.value.length
if(size==1) { if(size==1) {
// loop over string of length 1 -> just assign the single character // loop over string of length 1 -> just assign the single character
val character = CompilationTarget.instance.encodeString(sv.value, sv.altEncoding)[0] val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position) val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position) val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position)) scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
@ -291,22 +296,10 @@ internal class StatementOptimizer(private val program: Program,
return noModifications return noModifications
} }
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
// remove empty choices
class ChoiceRemover(val choice: WhenChoice) : IAstModification {
override fun perform() {
whenStatement.choices.remove(choice)
}
}
return whenStatement.choices
.filter { !it.statements.containsCodeOrVars() }
.map { ChoiceRemover(it) }
}
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> { override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump // if the jump is to the next statement, remove the jump
val scope = jump.definingScope() val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope) val label = jump.identifier?.targetStatement(program)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, jump.definingScope())) return listOf(IAstModification.Remove(jump, jump.definingScope()))
@ -390,7 +383,7 @@ internal class StatementOptimizer(private val program: Program,
// assignments of the form: X = X <operator> <expr> // assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0) // remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions // optimize/rewrite some other expressions
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
when (bexpr.operator) { when (bexpr.operator) {
"+" -> { "+" -> {
if (rightCv == 0.0) { if (rightCv == 0.0) {

View File

@ -3,24 +3,33 @@ package prog8.optimizer
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.ErrorReporter import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.* import prog8.ast.expressions.FunctionCall
import prog8.ast.processing.AstWalker import prog8.ast.expressions.PrefixExpression
import prog8.ast.processing.IAstModification import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal class UnusedCodeRemover(private val program: Program, private val errors: ErrorReporter): AstWalker() { internal class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget,
private val asmFileLoader: (filename: String, source: Path)->String): AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> { override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program) val callgraph = CallGraph(program, asmFileLoader)
val removals = mutableListOf<IAstModification>() val removals = mutableListOf<IAstModification>()
// remove all subroutines that aren't called, or are empty // remove all subroutines that aren't called, or are empty
val entrypoint = program.entrypoint() val entrypoint = program.entrypoint()
program.modules.forEach { program.modules.forEach {
callgraph.forAllSubroutines(it) { sub -> callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) { val forceOutput = "force_output" in sub.definingBlock().options()
if (sub !== entrypoint && !forceOutput && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
removals.add(IAstModification.Remove(sub, sub.definingScope())) removals.add(IAstModification.Remove(sub, sub.definingScope()))
} }
} }
@ -92,7 +101,7 @@ internal class UnusedCodeRemover(private val program: Program, private val error
val assign1 = stmtPairs[0] as? Assignment val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null && !assign2.isAugmentable) { if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace)) { if (assign1.target.isSameAs(assign2.target, program) && compTarget.isInRegularRAM(assign1.target, program)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray()))) if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
// only remove the second assignment if its value is a simple expression! // only remove the second assignment if its value is a simple expression!
when(assign2.value) { when(assign2.value) {

View File

@ -5,19 +5,24 @@ import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.base.* import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5 import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.cx16.CX16MachineDefinition import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
import java.io.CharConversionException import java.io.CharConversionException
import java.nio.file.Path
import kotlin.test.* import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -50,8 +55,8 @@ class TestCompiler {
assertEquals("-\$c382", (-50050).toHex()) assertEquals("-\$c382", (-50050).toHex())
assertEquals("-\$ffff", (-65535).toHex()) assertEquals("-\$ffff", (-65535).toHex())
assertEquals("-\$ffff", (-65535L).toHex()) assertEquals("-\$ffff", (-65535L).toHex())
assertFailsWith<CompilerException> { 65536.toHex() } assertFailsWith<IllegalArgumentException> { 65536.toHex() }
assertFailsWith<CompilerException> { 65536L.toHex() } assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
} }
@Test @Test
@ -130,7 +135,7 @@ class TestC64Zeropage {
@Test @Test
fun testNames() { fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
zp.allocate("", DataType.UBYTE, null, errors) zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, errors) zp.allocate("", DataType.UBYTE, null, errors)
@ -143,37 +148,37 @@ class TestC64Zeropage {
@Test @Test
fun testZpFloatEnable() { fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors) zp.allocate("", DataType.FLOAT, null, errors)
} }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors) zp2.allocate("", DataType.FLOAT, null, errors)
} }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false)) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
zp3.allocate("", DataType.FLOAT, null, errors) zp3.allocate("", DataType.FLOAT, null, errors)
} }
@Test @Test
fun testZpModesWithFloats() { fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
} }
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
} }
} }
@Test @Test
fun testZpDontuse() { fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
println(zp.free) println(zp.free)
assertEquals(0, zp.available()) assertEquals(0, zp.available())
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
@ -183,19 +188,19 @@ class TestC64Zeropage {
@Test @Test
fun testFreeSpaces() { fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false)) val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp1.available()) assertEquals(18, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(89, zp2.available()) assertEquals(89, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false)) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(125, zp3.available()) assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false)) val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp4.available()) assertEquals(238, zp4.available())
} }
@Test @Test
fun testReservedSpace() { fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false)) val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp1.available()) assertEquals(238, zp1.available())
assertTrue(50 in zp1.free) assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free) assertTrue(100 in zp1.free)
@ -204,7 +209,7 @@ class TestC64Zeropage {
assertTrue(200 in zp1.free) assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free) assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free) assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
assertEquals(139, zp2.available()) assertEquals(139, zp2.available())
assertFalse(50 in zp2.free) assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free) assertFalse(100 in zp2.free)
@ -217,7 +222,7 @@ class TestC64Zeropage {
@Test @Test
fun testBasicsafeAllocation() { fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available()) assertEquals(18, zp.available())
assertFailsWith<ZeropageDepletedError> { assertFailsWith<ZeropageDepletedError> {
@ -240,7 +245,7 @@ class TestC64Zeropage {
@Test @Test
fun testFullAllocation() { fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp.available()) assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.UWORD, null, errors) val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3) assertTrue(loc > 3)
@ -270,7 +275,7 @@ class TestC64Zeropage {
@Test @Test
fun testEfficientAllocation() { fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available()) assertEquals(18, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors)) assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors)) assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
@ -289,7 +294,7 @@ class TestC64Zeropage {
@Test @Test
fun testReservedLocations() { fun testReservedLocations() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word") assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
} }
} }
@ -299,9 +304,30 @@ class TestC64Zeropage {
class TestCx16Zeropage { class TestCx16Zeropage {
@Test @Test
fun testReservedLocations() { fun testReservedLocations() {
val zp = CX16MachineDefinition.CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false)) val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word") assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
} }
@Test
fun testFreeSpaces() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(88, zp1.available())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(175, zp3.available())
val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp1.available())
assertTrue(0x22 in zp1.free)
assertTrue(0x80 in zp1.free)
assertTrue(0xff in zp1.free)
assertFalse(0x02 in zp1.free)
assertFalse(0x21 in zp1.free)
}
} }
@ -401,74 +427,77 @@ class TestPetscii {
class TestMemory { class TestMemory {
private class DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
private class DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
@Test @Test
fun testInValidRamC64_memory_addresses() { fun testInValidRamC64_memory_addresses() {
CompilationTarget.instance = C64Target
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY) var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var scope = AnonymousScope(mutableListOf(), Position.DUMMY) val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(target.isInRegularRAM(scope)) assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAM(scope))
} }
@Test @Test
fun testNotInValidRamC64_memory_addresses() { fun testNotInValidRamC64_memory_addresses() {
CompilationTarget.instance = C64Target
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY) var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var scope = AnonymousScope(mutableListOf(), Position.DUMMY) val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(target.isInRegularRAM(scope)) assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAM(scope))
} }
@Test @Test
fun testInValidRamC64_memory_identifiers() { fun testInValidRamC64_memory_identifiers() {
CompilationTarget.instance = C64Target
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR) var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
assertTrue(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR) target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
assertFalse(target.isInRegularRAM(target.definingScope())) assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST) target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
assertTrue(target.isInRegularRAM(target.definingScope())) assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST) target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
assertFalse(target.isInRegularRAM(target.definingScope())) assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY) target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
assertFalse(target.isInRegularRAM(target.definingScope())) assertFalse(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
@ -478,89 +507,95 @@ class TestMemory {
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
module.linkParents(ParentSentinel)
return target return target
} }
@Test @Test
fun testInValidRamC64_memory_expression() { fun testInValidRamC64_memory_expression() {
CompilationTarget.instance = C64Target
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY) val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val scope = AnonymousScope(mutableListOf(), Position.DUMMY) val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(target.isInRegularRAM(scope)) assertFalse(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testInValidRamC64_variable() { fun testInValidRamC64_variable() {
CompilationTarget.instance = C64Target
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertTrue(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testInValidRamC64_memmap_variable() { fun testInValidRamC64_memmap_variable() {
CompilationTarget.instance = C64Target
val address = 0x1000 val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertTrue(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testNotInValidRamC64_memmap_variable() { fun testNotInValidRamC64_memmap_variable() {
CompilationTarget.instance = C64Target
val address = 0xd020 val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertFalse(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testInValidRamC64_array() { fun testInValidRamC64_array() {
CompilationTarget.instance = C64Target
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertTrue(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testInValidRamC64_array_memmapped() { fun testInValidRamC64_array_memmapped() {
CompilationTarget.instance = C64Target
val address = 0x1000 val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertTrue(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testNotValidRamC64_array_memmapped() { fun testNotValidRamC64_array_memmapped() {
CompilationTarget.instance = C64Target
val address = 0xe000 val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertFalse(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
} }
} }

67
compilerAst/build.gradle Normal file
View File

@ -0,0 +1,67 @@
plugins {
id 'antlr'
id 'java'
id "org.jetbrains.kotlin.jvm" version "1.4.30"
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenCentral()
}
configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar
compile {
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
}
}
dependencies {
antlr 'org.antlr:antlr4:4.9'
implementation 'org.antlr:antlr4-runtime:4.9'
implementation project(':parser')
// antlr('org.antlr:antlr4:4.9') {
// exclude group: 'com.ibm.icu', module: 'icu4j'
// }
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="antlr-runtime-4.9" level="project" />
</component>
</module>

View File

@ -5,9 +5,9 @@ import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.toHex import prog8.ast.walk.IAstVisitor
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor { class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
private var scopelevel = 0 private var scopelevel = 0
@ -413,10 +413,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
outputlni("}}") outputlni("}}")
} }
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
output(builtinFunctionStatementPlaceholder.name)
}
override fun visit(whenStatement: WhenStatement) { override fun visit(whenStatement: WhenStatement) {
output("when ") output("when ")
whenStatement.condition.accept(this) whenStatement.condition.accept(this)

View File

@ -1,14 +1,17 @@
package prog8.ast package prog8.ast
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.Expression import prog8.ast.expressions.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import java.nio.file.Path import java.nio.file.Path
import kotlin.math.abs
interface IStringEncoding {
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
}
interface Node { interface Node {
val position: Position val position: Position
@ -238,16 +241,47 @@ interface IAssignable {
// just a tag for now // just a tag for now
} }
interface IMemSizer {
fun memorySize(dt: DataType): Int
}
interface IBuiltinFunctions {
val names: Set<String>
val purefunctionNames: Set<String>
fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue?
fun returnType(name: String, args: MutableList<Expression>): InferredTypes.InferredType
}
/*********** Everything starts from here, the Program; zero or more modules *************/ /*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String, val modules: MutableList<Module>): Node {
val namespace = GlobalNamespace(modules)
class Program(val name: String,
val modules: MutableList<Module>,
val builtinFunctions: IBuiltinFunctions,
val memsizer: IMemSizer): Node {
val namespace = GlobalNamespace(modules, builtinFunctions.names)
val mainModule: Module
get() = modules.first { it.name!=internedStringsModuleName }
val definedLoadAddress: Int val definedLoadAddress: Int
get() = modules.first().loadAddress get() = mainModule.loadAddress
var actualLoadAddress: Int = 0 var actualLoadAddress: Int = 0
private val internedStrings = mutableMapOf<Pair<String, Boolean>, List<String>>()
val internedStringsModuleName = "prog8_interned_strings"
init {
// insert a container module for all interned strings later
if(modules.firstOrNull()?.name != internedStringsModuleName) {
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, false, Path.of(""))
modules.add(0, internedStringsModule)
val block = Block(internedStringsModuleName, null, mutableListOf(), false, Position.DUMMY)
internedStringsModule.statements.add(block)
internedStringsModule.linkParents(this)
internedStringsModule.program = this
}
}
fun entrypoint(): Subroutine? { fun entrypoint(): Subroutine? {
val mainBlocks = allBlocks().filter { it.name=="main" } val mainBlocks = allBlocks().filter { it.name=="main" }
@ -260,6 +294,22 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
} }
} }
fun internString(string: StringLiteralValue): List<String> {
val key = Pair(string.value, string.altEncoding)
val existing = internedStrings[key]
if(existing!=null)
return existing
val decl = VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, "string_${internedStrings.size}", null, string,
isArray = false, autogeneratedDontRemove = true, position = string.position)
val internedStringsBlock = modules.first { it.name==internedStringsModuleName }.statements.first { it is Block && it.name == internedStringsModuleName}
(internedStringsBlock as Block).statements.add(decl)
decl.linkParents(internedStringsBlock)
val scopedName = listOf(internedStringsModuleName, decl.name)
internedStrings[key] = scopedName
return scopedName
}
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() } fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
override val position: Position = Position.DUMMY override val position: Position = Position.DUMMY
@ -317,7 +367,7 @@ class Module(override val name: String,
} }
class GlobalNamespace(val modules: List<Module>): Node, INameScope { class GlobalNamespace(val modules: List<Module>, private val builtinFunctionNames: Set<String>): Node, INameScope {
override val name = "<<<global>>>" override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0) override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>() // not used override val statements = mutableListOf<Statement>() // not used
@ -332,7 +382,7 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
} }
override fun lookup(scopedName: List<String>, localContext: Node): Statement? { override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) { if (scopedName.size == 1 && scopedName[0] in builtinFunctionNames) {
// builtin functions always exist, return a dummy localContext for them // builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position) val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
builtinPlaceholder.parent = ParentSentinel builtinPlaceholder.parent = ParentSentinel
@ -379,4 +429,21 @@ object BuiltinFunctionScopePlaceholder : INameScope {
// prefix for struct member variables // prefix for struct member variables
internal fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName" fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName"
fun Number.toHex(): String {
// 0..15 -> "0".."15"
// 16..255 -> "$10".."$ff"
// 256..65536 -> "$0100".."$ffff"
// negative values are prefixed with '-'.
val integer = this.toInt()
if(integer<0)
return '-' + abs(integer).toHex()
return when (integer) {
in 0 until 16 -> integer.toString()
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
else -> throw IllegalArgumentException("number too large for 16 bits $this")
}
}

View File

@ -3,11 +3,11 @@ package prog8.ast.antlr
import org.antlr.v4.runtime.IntStream import org.antlr.v4.runtime.IntStream
import org.antlr.v4.runtime.ParserRuleContext import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.tree.TerminalNode import org.antlr.v4.runtime.tree.TerminalNode
import prog8.ast.IStringEncoding
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.parser.CustomLexer import prog8.parser.CustomLexer
import prog8.parser.prog8Parser import prog8.parser.prog8Parser
import java.io.CharConversionException import java.io.CharConversionException
@ -19,10 +19,10 @@ import java.nio.file.Path
private data class NumericLiteral(val number: Number, val datatype: DataType) private data class NumericLiteral(val number: Number, val datatype: DataType)
internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module { internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path, encoding: IStringEncoding) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
val directives = this.directive().map { it.toAst() } val directives = this.directive().map { it.toAst() }
val blocks = this.block().map { it.toAst(isLibrary) } val blocks = this.block().map { it.toAst(isLibrary, encoding) }
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), isLibrary, source) return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), isLibrary, source)
} }
@ -38,11 +38,11 @@ private fun ParserRuleContext.toPosition() : Position {
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length) return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
} }
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement { private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean, encoding: IStringEncoding) : Statement {
val blockstatements = block_statement().map { val blockstatements = block_statement().map {
when { when {
it.variabledeclaration()!=null -> it.variabledeclaration().toAst() it.variabledeclaration()!=null -> it.variabledeclaration().toAst(encoding)
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst() it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst(encoding)
it.directive()!=null -> it.directive().toAst() it.directive()!=null -> it.directive().toAst()
it.inlineasm()!=null -> it.inlineasm().toAst() it.inlineasm()!=null -> it.inlineasm().toAst()
else -> throw FatalAstException("weird block statement $it") else -> throw FatalAstException("weird block statement $it")
@ -51,11 +51,11 @@ private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement {
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition()) return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
} }
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> = private fun prog8Parser.Statement_blockContext.toAst(encoding: IStringEncoding): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList() statement().asSequence().map { it.toAst(encoding) }.toMutableList()
private fun prog8Parser.VariabledeclarationContext.toAst() : Statement { private fun prog8Parser.VariabledeclarationContext.toAst(encoding: IStringEncoding) : Statement {
vardecl()?.let { return it.toAst() } vardecl()?.let { return it.toAst(encoding) }
varinitializer()?.let { varinitializer()?.let {
val vd = it.vardecl() val vd = it.vardecl()
@ -63,10 +63,10 @@ private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
VarDeclType.VAR, VarDeclType.VAR,
vd.datatype()?.toAst() ?: DataType.STRUCT, vd.datatype()?.toAst() ?: DataType.STRUCT,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(), vd.arrayindex()?.toAst(encoding),
vd.varname.text, vd.varname.text,
null, null,
it.expression().toAst(), it.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null, vd.ARRAYSIG() != null || vd.arrayindex() != null,
false, false,
it.toPosition() it.toPosition()
@ -82,7 +82,7 @@ private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
null, null,
vd.varname.text, vd.varname.text,
vd.structname.text, vd.structname.text,
it.expression().toAst(), it.expression().toAst(encoding),
isArray = false, isArray = false,
autogeneratedDontRemove = false, autogeneratedDontRemove = false,
position = it.toPosition() position = it.toPosition()
@ -111,10 +111,10 @@ private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
VarDeclType.CONST, VarDeclType.CONST,
vd.datatype()?.toAst() ?: DataType.STRUCT, vd.datatype()?.toAst() ?: DataType.STRUCT,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(), vd.arrayindex()?.toAst(encoding),
vd.varname.text, vd.varname.text,
null, null,
cvarinit.expression().toAst(), cvarinit.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null, vd.ARRAYSIG() != null || vd.arrayindex() != null,
false, false,
cvarinit.toPosition() cvarinit.toPosition()
@ -128,10 +128,10 @@ private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
VarDeclType.MEMORY, VarDeclType.MEMORY,
vd.datatype()?.toAst() ?: DataType.STRUCT, vd.datatype()?.toAst() ?: DataType.STRUCT,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(), vd.arrayindex()?.toAst(encoding),
vd.varname.text, vd.varname.text,
null, null,
mvarinit.expression().toAst(), mvarinit.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null, vd.ARRAYSIG() != null || vd.arrayindex() != null,
false, false,
mvarinit.toPosition() mvarinit.toPosition()
@ -140,40 +140,40 @@ private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
structdecl()?.let { structdecl()?.let {
return StructDecl(it.identifier().text, return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(), it.vardecl().map { vd->vd.toAst(encoding) }.toMutableList(),
toPosition()) toPosition())
} }
throw FatalAstException("weird variable decl $this") throw FatalAstException("weird variable decl $this")
} }
private fun prog8Parser.SubroutinedeclarationContext.toAst() : Subroutine { private fun prog8Parser.SubroutinedeclarationContext.toAst(encoding: IStringEncoding) : Subroutine {
return when { return when {
subroutine()!=null -> subroutine().toAst() subroutine()!=null -> subroutine().toAst(encoding)
asmsubroutine()!=null -> asmsubroutine().toAst() asmsubroutine()!=null -> asmsubroutine().toAst(encoding)
romsubroutine()!=null -> romsubroutine().toAst() romsubroutine()!=null -> romsubroutine().toAst()
else -> throw FatalAstException("weird subroutine decl $this") else -> throw FatalAstException("weird subroutine decl $this")
} }
} }
private fun prog8Parser.StatementContext.toAst() : Statement { private fun prog8Parser.StatementContext.toAst(encoding: IStringEncoding) : Statement {
val vardecl = variabledeclaration()?.toAst() val vardecl = variabledeclaration()?.toAst(encoding)
if(vardecl!=null) return vardecl if(vardecl!=null) return vardecl
assignment()?.let { assignment()?.let {
return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition()) return Assignment(it.assign_target().toAst(encoding), it.expression().toAst(encoding), it.toPosition())
} }
augassignment()?.let { augassignment()?.let {
// replace A += X with A = A + X // replace A += X with A = A + X
val target = it.assign_target().toAst() val target = it.assign_target().toAst(encoding)
val oper = it.operator.text.substringBefore('=') val oper = it.operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition()) val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(encoding), it.expression().toPosition())
return Assignment(it.assign_target().toAst(), expression, it.toPosition()) return Assignment(it.assign_target().toAst(encoding), expression, it.toPosition())
} }
postincrdecr()?.let { postincrdecr()?.let {
return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition()) return PostIncrDecr(it.assign_target().toAst(encoding), it.operator.text, it.toPosition())
} }
val directive = directive()?.toAst() val directive = directive()?.toAst()
@ -185,49 +185,49 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val jump = unconditionaljump()?.toAst() val jump = unconditionaljump()?.toAst()
if(jump!=null) return jump if(jump!=null) return jump
val fcall = functioncall_stmt()?.toAst() val fcall = functioncall_stmt()?.toAst(encoding)
if(fcall!=null) return fcall if(fcall!=null) return fcall
val ifstmt = if_stmt()?.toAst() val ifstmt = if_stmt()?.toAst(encoding)
if(ifstmt!=null) return ifstmt if(ifstmt!=null) return ifstmt
val returnstmt = returnstmt()?.toAst() val returnstmt = returnstmt()?.toAst(encoding)
if(returnstmt!=null) return returnstmt if(returnstmt!=null) return returnstmt
val subroutine = subroutinedeclaration()?.toAst() val subroutine = subroutinedeclaration()?.toAst(encoding)
if(subroutine!=null) return subroutine if(subroutine!=null) return subroutine
val asm = inlineasm()?.toAst() val asm = inlineasm()?.toAst()
if(asm!=null) return asm if(asm!=null) return asm
val branchstmt = branch_stmt()?.toAst() val branchstmt = branch_stmt()?.toAst(encoding)
if(branchstmt!=null) return branchstmt if(branchstmt!=null) return branchstmt
val forloop = forloop()?.toAst() val forloop = forloop()?.toAst(encoding)
if(forloop!=null) return forloop if(forloop!=null) return forloop
val untilloop = untilloop()?.toAst() val untilloop = untilloop()?.toAst(encoding)
if(untilloop!=null) return untilloop if(untilloop!=null) return untilloop
val whileloop = whileloop()?.toAst() val whileloop = whileloop()?.toAst(encoding)
if(whileloop!=null) return whileloop if(whileloop!=null) return whileloop
val repeatloop = repeatloop()?.toAst() val repeatloop = repeatloop()?.toAst(encoding)
if(repeatloop!=null) return repeatloop if(repeatloop!=null) return repeatloop
val breakstmt = breakstmt()?.toAst() val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt if(breakstmt!=null) return breakstmt
val whenstmt = whenstmt()?.toAst() val whenstmt = whenstmt()?.toAst(encoding)
if(whenstmt!=null) return whenstmt if(whenstmt!=null) return whenstmt
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text") throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
} }
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine { private fun prog8Parser.AsmsubroutineContext.toAst(encoding: IStringEncoding): Subroutine {
val inline = this.inline()!=null val inline = this.inline()!=null
val subdecl = asmsub_decl().toAst() val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf() val statements = statement_block()?.toAst(encoding) ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes, return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters, subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, null, true, inline, statements, toPosition()) subdecl.asmClobbers, null, true, inline, statements, toPosition())
@ -308,28 +308,28 @@ private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParamete
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition()) AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
} }
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement { private fun prog8Parser.Functioncall_stmtContext.toAst(encoding: IStringEncoding): Statement {
val void = this.VOID() != null val void = this.VOID() != null
val location = scoped_identifier().toAst() val location = scoped_identifier().toAst()
return if(expression_list() == null) return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), void, toPosition()) FunctionCallStatement(location, mutableListOf(), void, toPosition())
else else
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition()) FunctionCallStatement(location, expression_list().toAst(encoding).toMutableList(), void, toPosition())
} }
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall { private fun prog8Parser.FunctioncallContext.toAst(encoding: IStringEncoding): FunctionCall {
val location = scoped_identifier().toAst() val location = scoped_identifier().toAst()
return if(expression_list() == null) return if(expression_list() == null)
FunctionCall(location, mutableListOf(), toPosition()) FunctionCall(location, mutableListOf(), toPosition())
else else
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition()) FunctionCall(location, expression_list().toAst(encoding).toMutableList(), toPosition())
} }
private fun prog8Parser.InlineasmContext.toAst() = private fun prog8Parser.InlineasmContext.toAst() =
InlineAssembly(INLINEASMBLOCK().text, toPosition()) InlineAssembly(INLINEASMBLOCK().text, toPosition())
private fun prog8Parser.ReturnstmtContext.toAst() : Return { private fun prog8Parser.ReturnstmtContext.toAst(encoding: IStringEncoding) : Return {
return Return(expression()?.toAst(), toPosition()) return Return(expression()?.toAst(encoding), toPosition())
} }
private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump { private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
@ -341,14 +341,14 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
private fun prog8Parser.LabeldefContext.toAst(): Statement = private fun prog8Parser.LabeldefContext.toAst(): Statement =
Label(children[0].text, toPosition()) Label(children[0].text, toPosition())
private fun prog8Parser.SubroutineContext.toAst() : Subroutine { private fun prog8Parser.SubroutineContext.toAst(encoding: IStringEncoding) : Subroutine {
// non-asm subroutine // non-asm subroutine
val inline = inline()!=null val inline = inline()!=null
val returntypes = sub_return_part()?.toAst() ?: emptyList() val returntypes = sub_return_part()?.toAst() ?: emptyList()
return Subroutine(identifier().text, return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(), sub_params()?.toAst() ?: emptyList(),
returntypes, returntypes,
statement_block()?.toAst() ?: mutableListOf(), statement_block()?.toAst(encoding) ?: mutableListOf(),
inline, inline,
toPosition()) toPosition())
} }
@ -364,12 +364,12 @@ private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
SubroutineParameter(it.varname.text, datatype, it.toPosition()) SubroutineParameter(it.varname.text, datatype, it.toPosition())
} }
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget { private fun prog8Parser.Assign_targetContext.toAst(encoding: IStringEncoding) : AssignTarget {
val identifier = scoped_identifier() val identifier = scoped_identifier()
return when { return when {
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition()) identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition()) arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(encoding), null, toPosition())
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition()) directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(encoding), toPosition()), toPosition())
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition()) else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
} }
} }
@ -381,8 +381,8 @@ private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase()) private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex = private fun prog8Parser.ArrayindexContext.toAst(encoding: IStringEncoding) : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition()) ArrayIndex(expression().toAst(encoding), toPosition())
private fun prog8Parser.DirectiveContext.toAst() : Directive = private fun prog8Parser.DirectiveContext.toAst() : Directive =
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition()) Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
@ -446,7 +446,7 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
} }
} }
private fun prog8Parser.ExpressionContext.toAst() : Expression { private fun prog8Parser.ExpressionContext.toAst(encoding: IStringEncoding) : Expression {
val litval = literalvalue() val litval = literalvalue()
if(litval!=null) { if(litval!=null) {
@ -469,7 +469,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
litval.stringliteral()!=null -> litval.stringliteral().toAst() litval.stringliteral()!=null -> litval.stringliteral().toAst()
litval.charliteral()!=null -> { litval.charliteral()!=null -> {
try { try {
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString( NumericLiteralValue(DataType.UBYTE, encoding.encodeString(
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()), unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition()) litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
} catch (ce: CharConversionException) { } catch (ce: CharConversionException) {
@ -477,7 +477,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
} }
} }
litval.arrayliteral()!=null -> { litval.arrayliteral()!=null -> {
val array = litval.arrayliteral().toAst() val array = litval.arrayliteral().toAst(encoding)
// the actual type of the arraysize can not yet be determined here (missing namespace & heap) // the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFold takes care of that and converts the type if needed. // the ConstantFold takes care of that and converts the type if needed.
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition()) ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
@ -491,31 +491,31 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
return scoped_identifier().toAst() return scoped_identifier().toAst()
if(bop!=null) if(bop!=null)
return BinaryExpression(left.toAst(), bop.text, right.toAst(), toPosition()) return BinaryExpression(left.toAst(encoding), bop.text, right.toAst(encoding), toPosition())
if(prefix!=null) if(prefix!=null)
return PrefixExpression(prefix.text, expression(0).toAst(), toPosition()) return PrefixExpression(prefix.text, expression(0).toAst(encoding), toPosition())
val funcall = functioncall()?.toAst() val funcall = functioncall()?.toAst(encoding)
if(funcall!=null) return funcall if(funcall!=null) return funcall
if (rangefrom!=null && rangeto!=null) { if (rangefrom!=null && rangeto!=null) {
val defaultstep = if(rto.text == "to") 1 else -1 val defaultstep = if(rto.text == "to") 1 else -1
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition()) val step = rangestep?.toAst(encoding) ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition()) return RangeExpr(rangefrom.toAst(encoding), rangeto.toAst(encoding), step, encoding, toPosition())
} }
if(childCount==3 && children[0].text=="(" && children[2].text==")") if(childCount==3 && children[0].text=="(" && children[2].text==")")
return expression(0).toAst() // expression within ( ) return expression(0).toAst(encoding) // expression within ( )
if(arrayindexed()!=null) if(arrayindexed()!=null)
return arrayindexed().toAst() return arrayindexed().toAst(encoding)
if(typecast()!=null) if(typecast()!=null)
return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition()) return TypecastExpression(expression(0).toAst(encoding), typecast().datatype().toAst(), false, toPosition())
if(directmemory()!=null) if(directmemory()!=null)
return DirectMemoryRead(directmemory().expression().toAst(), toPosition()) return DirectMemoryRead(directmemory().expression().toAst(encoding), toPosition())
if(addressof()!=null) if(addressof()!=null)
return AddressOf(addressof().scoped_identifier().toAst(), toPosition()) return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
@ -526,13 +526,13 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
private fun prog8Parser.StringliteralContext.toAst(): StringLiteralValue = private fun prog8Parser.StringliteralContext.toAst(): StringLiteralValue =
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition()) StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression { private fun prog8Parser.ArrayindexedContext.toAst(encoding: IStringEncoding): ArrayIndexedExpression {
return ArrayIndexedExpression(scoped_identifier().toAst(), return ArrayIndexedExpression(scoped_identifier().toAst(),
arrayindex().toAst(), arrayindex().toAst(encoding),
toPosition()) toPosition())
} }
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() } private fun prog8Parser.Expression_listContext.toAst(encoding: IStringEncoding) = expression().map{ it.toAst(encoding) }
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference = private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
IdentifierReference(listOf(text), toPosition()) IdentifierReference(listOf(text), toPosition())
@ -548,27 +548,27 @@ private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
else -> throw FatalAstException(text) else -> throw FatalAstException(text)
} }
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> = private fun prog8Parser.ArrayliteralContext.toAst(encoding: IStringEncoding) : Array<Expression> =
expression().map { it.toAst() }.toTypedArray() expression().map { it.toAst(encoding) }.toTypedArray()
private fun prog8Parser.If_stmtContext.toAst(): IfStatement { private fun prog8Parser.If_stmtContext.toAst(encoding: IStringEncoding): IfStatement {
val condition = expression().toAst() val condition = expression().toAst(encoding)
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) val trueStatements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
val elseStatements = else_part()?.toAst() ?: mutableListOf() val elseStatements = else_part()?.toAst(encoding) ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition() val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
?: statement().toPosition()) ?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition()) val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
return IfStatement(condition, trueScope, elseScope, toPosition()) return IfStatement(condition, trueScope, elseScope, toPosition())
} }
private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> { private fun prog8Parser.Else_partContext.toAst(encoding: IStringEncoding): MutableList<Statement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst()) return statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
} }
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement { private fun prog8Parser.Branch_stmtContext.toAst(encoding: IStringEncoding): BranchStatement {
val branchcondition = branchcondition().toAst() val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) val trueStatements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
val elseStatements = else_part()?.toAst() ?: mutableListOf() val elseStatements = else_part()?.toAst(encoding) ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition() val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
?: statement().toPosition()) ?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition()) val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
@ -577,65 +577,65 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase()) private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
private fun prog8Parser.ForloopContext.toAst(): ForLoop { private fun prog8Parser.ForloopContext.toAst(encoding: IStringEncoding): ForLoop {
val loopvar = identifier().toAst() val loopvar = identifier().toAst()
val iterable = expression()!!.toAst() val iterable = expression()!!.toAst(encoding)
val scope = val scope =
if(statement()!=null) if(statement()!=null)
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition()) AnonymousScope(mutableListOf(statement().toAst(encoding)), statement().toPosition())
else else
AnonymousScope(statement_block().toAst(), statement_block().toPosition()) AnonymousScope(statement_block().toAst(encoding), statement_block().toPosition())
return ForLoop(loopvar, iterable, scope, toPosition()) return ForLoop(loopvar, iterable, scope, toPosition())
} }
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition()) private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop { private fun prog8Parser.WhileloopContext.toAst(encoding: IStringEncoding): WhileLoop {
val condition = expression().toAst() val condition = expression().toAst(encoding)
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
val scope = AnonymousScope(statements, statement_block()?.toPosition() val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition()) ?: statement().toPosition())
return WhileLoop(condition, scope, toPosition()) return WhileLoop(condition, scope, toPosition())
} }
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop { private fun prog8Parser.RepeatloopContext.toAst(encoding: IStringEncoding): RepeatLoop {
val iterations = expression()?.toAst() val iterations = expression()?.toAst(encoding)
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
val scope = AnonymousScope(statements, statement_block()?.toPosition() val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition()) ?: statement().toPosition())
return RepeatLoop(iterations, scope, toPosition()) return RepeatLoop(iterations, scope, toPosition())
} }
private fun prog8Parser.UntilloopContext.toAst(): UntilLoop { private fun prog8Parser.UntilloopContext.toAst(encoding: IStringEncoding): UntilLoop {
val untilCondition = expression().toAst() val untilCondition = expression().toAst(encoding)
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
val scope = AnonymousScope(statements, statement_block()?.toPosition() val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition()) ?: statement().toPosition())
return UntilLoop(scope, untilCondition, toPosition()) return UntilLoop(scope, untilCondition, toPosition())
} }
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement { private fun prog8Parser.WhenstmtContext.toAst(encoding: IStringEncoding): WhenStatement {
val condition = expression().toAst() val condition = expression().toAst(encoding)
val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf() val choices = this.when_choice()?.map { it.toAst(encoding) }?.toMutableList() ?: mutableListOf()
return WhenStatement(condition, choices, toPosition()) return WhenStatement(condition, choices, toPosition())
} }
private fun prog8Parser.When_choiceContext.toAst(): WhenChoice { private fun prog8Parser.When_choiceContext.toAst(encoding: IStringEncoding): WhenChoice {
val values = expression_list()?.toAst() val values = expression_list()?.toAst(encoding)
val stmt = statement()?.toAst() val stmt = statement()?.toAst(encoding)
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf() val stmtBlock = statement_block()?.toAst(encoding)?.toMutableList() ?: mutableListOf()
if(stmt!=null) if(stmt!=null)
stmtBlock.add(stmt) stmtBlock.add(stmt)
val scope = AnonymousScope(stmtBlock, toPosition()) val scope = AnonymousScope(stmtBlock, toPosition())
return WhenChoice(values?.toMutableList(), scope, toPosition()) return WhenChoice(values?.toMutableList(), scope, toPosition())
} }
private fun prog8Parser.VardeclContext.toAst(): VarDecl { private fun prog8Parser.VardeclContext.toAst(encoding: IStringEncoding): VarDecl {
return VarDecl( return VarDecl(
VarDeclType.VAR, VarDeclType.VAR,
datatype()?.toAst() ?: DataType.STRUCT, datatype()?.toAst() ?: DataType.STRUCT,
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE, if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
arrayindex()?.toAst(), arrayindex()?.toAst(encoding),
varname.text, varname.text,
null, null,
null, null,
@ -644,48 +644,3 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
toPosition() toPosition()
) )
} }
internal fun escape(str: String): String {
val es = str.map {
when(it) {
'\t' -> "\\t"
'\n' -> "\\n"
'\r' -> "\\r"
'"' -> "\\\""
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
in '\u0000'..'\u00ff' -> it.toString()
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
}
}
return es.joinToString("")
}
internal fun unescape(str: String, position: Position): String {
val result = mutableListOf<Char>()
val iter = str.iterator()
while(iter.hasNext()) {
val c = iter.nextChar()
if(c=='\\') {
val ec = iter.nextChar()
result.add(when(ec) {
'\\' -> '\\'
'n' -> '\n'
'r' -> '\r'
'"' -> '"'
'\'' -> '\''
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
'x' -> {
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
(0x8000 + hex).toChar()
}
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
})
} else {
result.add(c)
}
}
return result.joinToString("")
}

View File

@ -0,0 +1,49 @@
package prog8.ast.antlr
import prog8.ast.base.Position
import prog8.ast.base.SyntaxError
fun escape(str: String): String {
val es = str.map {
when(it) {
'\t' -> "\\t"
'\n' -> "\\n"
'\r' -> "\\r"
'"' -> "\\\""
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
in '\u0000'..'\u00ff' -> it.toString()
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
}
}
return es.joinToString("")
}
fun unescape(str: String, position: Position): String {
val result = mutableListOf<Char>()
val iter = str.iterator()
while(iter.hasNext()) {
val c = iter.nextChar()
if(c=='\\') {
val ec = iter.nextChar()
result.add(when(ec) {
'\\' -> '\\'
'n' -> '\n'
'r' -> '\r'
'"' -> '"'
'\'' -> '\''
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
'x' -> {
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
(0x8000 + hex).toChar()
}
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
})
} else {
result.add(c)
}
}
return result.joinToString("")
}

View File

@ -1,7 +1,6 @@
package prog8.ast.base package prog8.ast.base
import prog8.ast.Node import prog8.ast.Node
import prog8.compiler.target.CompilationTarget
/**************************** AST Data classes ****************************/ /**************************** AST Data classes ****************************/
@ -44,7 +43,7 @@ enum class DataType {
this == other -> false this == other -> false
this in ByteDatatypes -> false this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes this in WordDatatypes -> other in ByteDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> false this== STR && other== UWORD || this== UWORD && other== STR -> false
else -> true else -> true
} }
@ -53,19 +52,9 @@ enum class DataType {
this == other -> true this == other -> true
this in ByteDatatypes -> other in ByteDatatypes this in ByteDatatypes -> other in ByteDatatypes
this in WordDatatypes -> other in WordDatatypes this in WordDatatypes -> other in WordDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> true this== STR && other== UWORD || this== UWORD && other== STR -> true
else -> false else -> false
} }
fun memorySize(): Int {
return when(this) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
FLOAT -> CompilationTarget.instance.machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> CompilationTarget.instance.machine.POINTER_MEM_SIZE
else -> -9999999
}
}
} }
enum class CpuRegister { enum class CpuRegister {
@ -91,6 +80,13 @@ enum class RegisterOrPair {
val names by lazy { values().map { it.toString()} } val names by lazy { values().map { it.toString()} }
} }
fun asCpuRegister(): CpuRegister = when(this) {
A -> CpuRegister.A
X -> CpuRegister.X
Y -> CpuRegister.Y
else -> throw IllegalArgumentException("no cpu hardware register for $this")
}
} // only used in parameter and return value specs in asm subroutines } // only used in parameter and return value specs in asm subroutines
enum class Statusflag { enum class Statusflag {
@ -135,7 +131,8 @@ val IterableDatatypes = setOf(
DataType.STR, DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F) DataType.ARRAY_F
)
val PassByValueDatatypes = NumericDatatypes val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT) val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
val ArrayElementTypes = mapOf( val ArrayElementTypes = mapOf(
@ -144,7 +141,8 @@ val ArrayElementTypes = mapOf(
DataType.ARRAY_UB to DataType.UBYTE, DataType.ARRAY_UB to DataType.UBYTE,
DataType.ARRAY_W to DataType.WORD, DataType.ARRAY_W to DataType.WORD,
DataType.ARRAY_UW to DataType.UWORD, DataType.ARRAY_UW to DataType.UWORD,
DataType.ARRAY_F to DataType.FLOAT) DataType.ARRAY_F to DataType.FLOAT
)
val ElementArrayTypes = mapOf( val ElementArrayTypes = mapOf(
DataType.BYTE to DataType.ARRAY_B, DataType.BYTE to DataType.ARRAY_B,
DataType.UBYTE to DataType.ARRAY_UB, DataType.UBYTE to DataType.ARRAY_UB,
@ -152,10 +150,12 @@ val ElementArrayTypes = mapOf(
DataType.UWORD to DataType.ARRAY_UW, DataType.UWORD to DataType.ARRAY_UW,
DataType.FLOAT to DataType.ARRAY_F DataType.FLOAT to DataType.ARRAY_F
) )
val Cx16VirtualRegisters = listOf(RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3, val Cx16VirtualRegisters = listOf(
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7, RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
RegisterOrPair.R8, RegisterOrPair.R9, RegisterOrPair.R10, RegisterOrPair.R11, RegisterOrPair.R8, RegisterOrPair.R9, RegisterOrPair.R10, RegisterOrPair.R11,
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15) RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
// find the parent node of a specific type or interface // find the parent node of a specific type or interface

View File

@ -3,14 +3,9 @@ package prog8.ast.expressions
import prog8.ast.* import prog8.ast.*
import prog8.ast.antlr.escape import prog8.ast.antlr.escape
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget import prog8.ast.walk.AstWalker
import prog8.functions.BuiltinFunctions import prog8.ast.walk.IAstVisitor
import prog8.functions.CannotEvaluateException
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import java.util.* import java.util.*
import kotlin.math.abs import kotlin.math.abs
@ -18,6 +13,8 @@ import kotlin.math.abs
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=") val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=") val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor") val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
val logicalOperators = setOf("and", "or", "xor", "not")
sealed class Expression: Node { sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue? abstract fun constValue(program: Program): NumericLiteralValue?
@ -152,10 +149,13 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
InferredTypes.unknown() InferredTypes.unknown()
else { else {
try { try {
InferredTypes.knownFor(commonDatatype( InferredTypes.knownFor(
commonDatatype(
leftDt.typeOrElse(DataType.BYTE), leftDt.typeOrElse(DataType.BYTE),
rightDt.typeOrElse(DataType.BYTE), rightDt.typeOrElse(DataType.BYTE),
null, null).first) null, null
).first
)
} catch (x: FatalAstException) { } catch (x: FatalAstException) {
InferredTypes.unknown() InferredTypes.unknown()
} }
@ -257,7 +257,7 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName) override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType { override fun inferType(program: Program): InferredTypes.InferredType {
val target = arrayvar.targetStatement(program.namespace) val target = arrayvar.targetStatement(program)
if (target is VarDecl) { if (target is VarDecl) {
return when (target.datatype) { return when (target.datatype) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE) DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
@ -480,15 +480,11 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
} }
} }
private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
class StringLiteralValue(val value: String, class StringLiteralValue(val value: String,
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64 val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
override val position: Position) : Expression() { override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
} }
@ -518,8 +514,6 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
override val position: Position) : Expression() { override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
value.forEach {it.linkParents(this)} value.forEach {it.linkParents(this)}
@ -612,6 +606,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
class RangeExpr(var from: Expression, class RangeExpr(var from: Expression,
var to: Expression, var to: Expression,
var step: Expression, var step: Expression,
private val encoding: IStringEncoding,
override val position: Position) : Expression() { override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
@ -677,8 +672,8 @@ class RangeExpr(var from: Expression,
val toString = to as? StringLiteralValue val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) { if(fromString!=null && toString!=null ) {
// string range -> int range over character values // string range -> int range over character values
fromVal = CompilationTarget.instance.encodeString(fromString.value, fromString.altEncoding)[0].toInt() fromVal = encoding.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = CompilationTarget.instance.encodeString(toString.value, fromString.altEncoding)[0].toInt() toVal = encoding.encodeString(toString.value, fromString.altEncoding)[0].toInt()
} else { } else {
val fromLv = from as? NumericLiteralValue val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue val toLv = to as? NumericLiteralValue
@ -708,17 +703,18 @@ internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
} }
} }
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable { data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(),
IAssignable {
override lateinit var parent: Node override lateinit var parent: Node
fun targetStatement(namespace: INameScope) = fun targetStatement(program: Program) =
if(nameInSource.size==1 && nameInSource[0] in BuiltinFunctions) if(nameInSource.size==1 && nameInSource[0] in program.builtinFunctions.names)
BuiltinFunctionStatementPlaceholder(nameInSource[0], position) BuiltinFunctionStatementPlaceholder(nameInSource[0], position)
else else
namespace.lookup(nameInSource, this) program.namespace.lookup(nameInSource, this)
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl fun targetVarDecl(program: Program): VarDecl? = targetStatement(program) as? VarDecl
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine fun targetSubroutine(program: Program): Subroutine? = targetStatement(program) as? Subroutine
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
override fun hashCode() = nameInSource.hashCode() override fun hashCode() = nameInSource.hashCode()
@ -754,31 +750,20 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName) nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
override fun inferType(program: Program): InferredTypes.InferredType { override fun inferType(program: Program): InferredTypes.InferredType {
return when (val targetStmt = targetStatement(program.namespace)) { return when (val targetStmt = targetStatement(program)) {
is VarDecl -> InferredTypes.knownFor(targetStmt.datatype) is VarDecl -> InferredTypes.knownFor(targetStmt.datatype)
is StructDecl -> InferredTypes.knownFor(DataType.STRUCT) is StructDecl -> InferredTypes.knownFor(DataType.STRUCT)
else -> InferredTypes.InferredType.unknown() else -> InferredTypes.InferredType.unknown()
} }
} }
fun memberOfStruct(namespace: INameScope) = this.targetVarDecl(namespace)?.struct fun memberOfStruct(program: Program) = this.targetVarDecl(program)?.struct
fun heapId(namespace: INameScope): Int { fun firstStructVarName(program: Program): String? {
val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this)
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
return when (value) {
is IdentifierReference -> value.heapId(namespace)
is StringLiteralValue -> value.heapId
is ArrayLiteralValue -> value.heapId
else -> throw FatalAstException("requires a reference value")
}
}
fun firstStructVarName(namespace: INameScope): String? {
// take the name of the first struct member of the structvariable instead // take the name of the first struct member of the structvariable instead
// if it's just a regular variable, return null. // if it's just a regular variable, return null.
val struct = memberOfStruct(namespace) ?: return null val struct = memberOfStruct(program) ?: return null
val decl = targetVarDecl(namespace)!! val decl = targetVarDecl(program)!!
if(decl.datatype!=DataType.STRUCT) if(decl.datatype!=DataType.STRUCT)
return null return null
@ -816,18 +801,9 @@ class FunctionCall(override var target: IdentifierReference,
private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteralValue? { private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteralValue? {
// if the function is a built-in function and the args are consts, should try to const-evaluate! // if the function is a built-in function and the args are consts, should try to const-evaluate!
// lenghts of arrays and strings are constants that are determined at compile time! // lenghts of arrays and strings are constants that are determined at compile time!
if(target.nameInSource.size>1) return null if(target.nameInSource.size>1)
try { return null
var resultValue: NumericLiteralValue? = null val resultValue: NumericLiteralValue? = program.builtinFunctions.constValue(target.nameInSource[0], args, position, program.memsizer)
val func = BuiltinFunctions[target.nameInSource[0]]
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null)
resultValue = exprfunc(args, position, program)
else if(func.known_returntype==null)
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
}
if(withDatatypeCheck) { if(withDatatypeCheck) {
val resultDt = this.inferType(program) val resultDt = this.inferType(program)
if(resultValue==null || resultDt istype resultValue.type) if(resultValue==null || resultDt istype resultValue.type)
@ -837,15 +813,6 @@ class FunctionCall(override var target: IdentifierReference,
return resultValue return resultValue
} }
} }
catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed.
return null
}
catch(x: CannotEvaluateException) {
// const-evaluating the builtin function call failed.
return null
}
}
override fun toString(): String { override fun toString(): String {
return "FunctionCall(target=$target, pos=$position)" return "FunctionCall(target=$target, pos=$position)"
@ -860,14 +827,14 @@ class FunctionCall(override var target: IdentifierReference,
val constVal = constValue(program ,false) val constVal = constValue(program ,false)
if(constVal!=null) if(constVal!=null)
return InferredTypes.knownFor(constVal.type) return InferredTypes.knownFor(constVal.type)
val stmt = target.targetStatement(program.namespace) ?: return InferredTypes.unknown() val stmt = target.targetStatement(program) ?: return InferredTypes.unknown()
when (stmt) { when (stmt) {
is BuiltinFunctionStatementPlaceholder -> { is BuiltinFunctionStatementPlaceholder -> {
if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" || if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" ||
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") { target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return InferredTypes.void() // these have no return value return InferredTypes.void() // these have no return value
} }
return builtinFunctionReturnType(target.nameInSource[0], this.args, program) return program.builtinFunctions.returnType(target.nameInSource[0], this.args)
} }
is Subroutine -> { is Subroutine -> {
if(stmt.returntypes.isEmpty()) if(stmt.returntypes.isEmpty())

View File

@ -3,10 +3,8 @@ package prog8.ast.statements
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.processing.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilerException
import prog8.compiler.target.CompilationTarget
sealed class Statement : Node { sealed class Statement : Node {
@ -36,8 +34,8 @@ sealed class Statement : Node {
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() { class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
override var parent: Node = ParentSentinel override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {} override fun linkParents(parent: Node) {}
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = throw FatalAstException("should not iterate over this node")
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = throw FatalAstException("should not iterate over this node")
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this replacement.parent = this
@ -178,12 +176,6 @@ open class VarDecl(val type: VarDeclType,
companion object { companion object {
private var autoHeapValueSequenceNumber = 0 private var autoHeapValueSequenceNumber = 0
fun createAuto(string: StringLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
return VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string,
isArray = false, autogeneratedDontRemove = true, position = string.position)
}
fun createAuto(array: ArrayLiteralValue): VarDecl { fun createAuto(array: ArrayLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}" val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val arrayDt = val arrayDt =
@ -252,7 +244,7 @@ open class VarDecl(val type: VarDeclType,
if(allowInitializeWithZero) if(allowInitializeWithZero)
return defaultZero(declaredDatatype, position) return defaultZero(declaredDatatype, position)
else else
throw CompilerException("attempt to get zero value for vardecl that shouldn't get it") throw IllegalArgumentException("attempt to get zero value for vardecl that shouldn't get it")
} }
fun flattenStructMembers(): MutableList<Statement> { fun flattenStructMembers(): MutableList<Statement> {
@ -518,44 +510,6 @@ data class AssignTarget(var identifier: IdentifierReference?,
} }
return false return false
} }
fun isInRegularRAM(namespace: INameScope): Boolean {
when {
this.memoryAddress != null -> {
return when (this.memoryAddress.addressExpression) {
is NumericLiteralValue -> {
CompilationTarget.instance.machine.isRegularRAMaddress((this.memoryAddress.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val decl = (this.memoryAddress.addressExpression as IdentifierReference).targetVarDecl(namespace)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
CompilationTarget.instance.machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
this.arrayindexed != null -> {
val targetStmt = this.arrayindexed!!.arrayvar.targetVarDecl(namespace)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
CompilationTarget.instance.machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
this.identifier != null -> {
val decl = this.identifier!!.targetVarDecl(namespace)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
CompilationTarget.instance.machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
}
}
} }

View File

@ -1,4 +1,4 @@
package prog8.ast.processing package prog8.ast.walk
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
@ -16,7 +16,8 @@ interface IAstModification {
} }
} }
class SetExpression(private val setter: (newExpr: Expression) -> Unit, private val newExpr: Expression, private val parent: Node) : IAstModification { class SetExpression(private val setter: (newExpr: Expression) -> Unit, private val newExpr: Expression, private val parent: Node) :
IAstModification {
override fun perform() { override fun perform() {
setter(newExpr) setter(newExpr)
newExpr.linkParents(parent) newExpr.linkParents(parent)
@ -37,7 +38,8 @@ interface IAstModification {
} }
} }
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification { class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) :
IAstModification {
override fun perform() { override fun perform() {
val idx = parent.statements.indexOfFirst { it===after } + 1 val idx = parent.statements.indexOfFirst { it===after } + 1
parent.statements.add(idx, stmt) parent.statements.add(idx, stmt)
@ -45,7 +47,8 @@ interface IAstModification {
} }
} }
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification { class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) :
IAstModification {
override fun perform() { override fun perform() {
val idx = parent.statements.indexOfFirst { it===before } val idx = parent.statements.indexOfFirst { it===before }
parent.statements.add(idx, stmt) parent.statements.add(idx, stmt)
@ -53,7 +56,8 @@ interface IAstModification {
} }
} }
class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) : IAstModification { class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) :
IAstModification {
override fun perform() { override fun perform() {
parent.replaceChildNode(node, replacement) parent.replaceChildNode(node, replacement)
replacement.linkParents(parent) replacement.linkParents(parent)
@ -80,7 +84,6 @@ abstract class AstWalker {
open fun before(block: Block, parent: Node): Iterable<IAstModification> = emptyList() open fun before(block: Block, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList() open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList() open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList() open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList() open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList() open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
@ -391,11 +394,6 @@ abstract class AstWalker {
track(after(inlineAssembly, parent), inlineAssembly, parent) track(after(inlineAssembly, parent), inlineAssembly, parent)
} }
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) {
track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
}
fun visit(nopStatement: NopStatement, parent: Node) { fun visit(nopStatement: NopStatement, parent: Node) {
track(before(nopStatement, parent), nopStatement, parent) track(before(nopStatement, parent), nopStatement, parent)
track(after(nopStatement, parent), nopStatement, parent) track(after(nopStatement, parent), nopStatement, parent)

View File

@ -1,4 +1,4 @@
package prog8.ast.processing package prog8.ast.walk
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
@ -158,9 +158,6 @@ interface IAstVisitor {
fun visit(inlineAssembly: InlineAssembly) { fun visit(inlineAssembly: InlineAssembly) {
} }
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
}
fun visit(nopStatement: NopStatement) { fun visit(nopStatement: NopStatement) {
} }

View File

@ -3,7 +3,6 @@ package prog8.parser
import org.antlr.v4.runtime.CommonTokenStream import org.antlr.v4.runtime.CommonTokenStream
import org.antlr.v4.runtime.Lexer import org.antlr.v4.runtime.Lexer
internal class CommentHandlingTokenStream(lexer: Lexer) : CommonTokenStream(lexer) { internal class CommentHandlingTokenStream(lexer: Lexer) : CommonTokenStream(lexer) {
data class Comment(val type: String, val line: Int, val comment: String) data class Comment(val type: String, val line: Int, val comment: String)

View File

@ -1,34 +1,33 @@
package prog8.parser package prog8.parser
import org.antlr.v4.runtime.* import org.antlr.v4.runtime.*
import prog8.ast.IStringEncoding
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.antlr.toAst import prog8.ast.antlr.toAst
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.base.SyntaxError import prog8.ast.base.SyntaxError
import prog8.ast.base.checkImportedValid
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg import prog8.ast.statements.DirectiveArg
import prog8.compiler.target.CompilationTarget
import prog8.pathFrom
import java.io.InputStream import java.io.InputStream
import java.nio.file.FileSystems
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
internal class ParsingFailedError(override var message: String) : Exception(message) class ParsingFailedError(override var message: String) : Exception(message)
internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input) internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.') internal fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
internal class ModuleImporter { class ModuleImporter {
internal fun importModule(program: Program, filePath: Path): Module { fun importModule(program: Program, filePath: Path, encoder: IStringEncoding, compilationTargetName: String): Module {
print("importing '${moduleName(filePath.fileName)}'") print("importing '${moduleName(filePath.fileName)}'")
if(filePath.parent!=null) { if(filePath.parent!=null) {
var importloc = filePath.toString() var importloc = filePath.toString()
@ -43,14 +42,15 @@ internal class ModuleImporter {
throw ParsingFailedError("No such file: $filePath") throw ParsingFailedError("No such file: $filePath")
val input = CharStreams.fromPath(filePath) val input = CharStreams.fromPath(filePath)
return importModule(program, input, filePath, false) return importModule(program, input, filePath, false, encoder, compilationTargetName)
} }
internal fun importLibraryModule(program: Program, name: String): Module? { fun importLibraryModule(program: Program, name: String,
encoder: IStringEncoding, compilationTargetName: String): Module? {
val import = Directive("%import", listOf( val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0)) DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0)) ), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(program, import, Paths.get("")) return executeImportDirective(program, import, Paths.get(""), encoder, compilationTargetName)
} }
private class MyErrorListener: ConsoleErrorListener() { private class MyErrorListener: ConsoleErrorListener() {
@ -65,7 +65,8 @@ internal class ModuleImporter {
} }
} }
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module { private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean,
encoder: IStringEncoding, compilationTargetName: String): Module {
val moduleName = moduleName(modulePath.fileName) val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream) val lexer = CustomLexer(modulePath, stream)
lexer.removeErrorListeners() lexer.removeErrorListeners()
@ -84,7 +85,7 @@ internal class ModuleImporter {
// tokens.commentTokens().forEach { println(it) } // tokens.commentTokens().forEach { println(it) }
// convert to Ast // convert to Ast
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath) val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath, encoder)
moduleAst.program = program moduleAst.program = program
moduleAst.linkParents(program.namespace) moduleAst.linkParents(program.namespace)
program.modules.add(moduleAst) program.modules.add(moduleAst)
@ -94,13 +95,14 @@ internal class ModuleImporter {
lines.asSequence() lines.asSequence()
.mapIndexed { i, it -> i to it } .mapIndexed { i, it -> i to it }
.filter { (it.second as? Directive)?.directive == "%import" } .filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(program, it.second as Directive, modulePath) } .forEach { executeImportDirective(program, it.second as Directive, modulePath, encoder, compilationTargetName) }
moduleAst.statements = lines moduleAst.statements = lines
return moduleAst return moduleAst
} }
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? { private fun executeImportDirective(program: Program, import: Directive, source: Path,
encoder: IStringEncoding, compilationTargetName: String): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null) if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position) throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!! val moduleName = import.args[0].name!!
@ -111,27 +113,36 @@ internal class ModuleImporter {
if(existing!=null) if(existing!=null)
return null return null
val rsc = tryGetModuleFromResource("$moduleName.p8") val rsc = tryGetModuleFromResource("$moduleName.p8", compilationTargetName)
val importedModule = val importedModule =
if(rsc!=null) { if(rsc!=null) {
// load the module from the embedded resource // load the module from the embedded resource
val (resource, resourcePath) = rsc val (resource, resourcePath) = rsc
resource.use { resource.use {
println("importing '$moduleName' (library)") println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"), true) importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"),
true, encoder, compilationTargetName)
} }
} else { } else {
val modulePath = tryGetModuleFromFile(moduleName, source, import.position) val modulePath = tryGetModuleFromFile(moduleName, source, import.position)
importModule(program, modulePath) importModule(program, modulePath, encoder, compilationTargetName)
} }
importedModule.checkImportedValid() removeDirectivesFromImportedModule(importedModule)
return importedModule return importedModule
} }
private fun tryGetModuleFromResource(name: String): Pair<InputStream, String>? { private fun removeDirectivesFromImportedModule(importedModule: Module) {
val target = CompilationTarget.instance.name // Most global directives don't apply for imported modules, so remove them
val targetSpecificPath = "/prog8lib/$target/$name" val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%target")
var directives = importedModule.statements.filterIsInstance<Directive>()
importedModule.statements.removeAll(directives)
directives = directives.filter{ it.directive !in moduleLevelDirectives }
importedModule.statements.addAll(0, directives)
}
private fun tryGetModuleFromResource(name: String, compilationTargetName: String): Pair<InputStream, String>? {
val targetSpecificPath = "/prog8lib/$compilationTargetName/$name"
val targetSpecificResource = object{}.javaClass.getResourceAsStream(targetSpecificPath) val targetSpecificResource = object{}.javaClass.getResourceAsStream(targetSpecificPath)
if(targetSpecificResource!=null) if(targetSpecificResource!=null)
return Pair(targetSpecificResource, targetSpecificPath) return Pair(targetSpecificResource, targetSpecificPath)

View File

@ -2,7 +2,7 @@
plugins { plugins {
id 'java' id 'java'
id 'application' id 'application'
id "org.jetbrains.kotlin.jvm" version "1.4.21" id "org.jetbrains.kotlin.jvm" version "1.4.30"
id 'com.github.johnrengelman.shadow' version '6.1.0' id 'com.github.johnrengelman.shadow' version '6.1.0'
} }

View File

@ -5,7 +5,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.8 virtualenv" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.9" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

View File

@ -20,7 +20,7 @@ import os
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = 'Prog8' project = 'Prog8'
copyright = '2019, Irmen de Jong' copyright = '2021, Irmen de Jong'
author = 'Irmen de Jong' author = 'Irmen de Jong'

View File

@ -57,7 +57,7 @@ Language features
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting. - High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
- Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse`` - Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the C64. - Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the C64.
- 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)! - If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
Code example Code example

View File

@ -182,7 +182,7 @@ Provides string manipulation routines.
floats floats
------ ------
Provides definitions for the ROM/kernel subroutines and utility routines dealing with floating Provides definitions for the ROM/kernal subroutines and utility routines dealing with floating
point variables. This includes ``print_f``, the routine used to print floating point numbers. point variables. This includes ``print_f``, the routine used to print floating point numbers.

View File

@ -381,13 +381,20 @@ Direct access to memory locations
Normally memory locations are accessed by a *memory mapped* name, such as ``c64.BGCOL0`` that is defined Normally memory locations are accessed by a *memory mapped* name, such as ``c64.BGCOL0`` that is defined
as the memory mapped address $d021. as the memory mapped address $d021.
If you want to access a memory location directly (by using the address itself), without defining If you want to access a memory location directly (by using the address itself or via an uword pointer variable),
a memory mapped location, you can do so by enclosing the address in ``@(...)``:: without defining a memory mapped location, you can do so by enclosing the address in ``@(...)``::
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)") color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0") @($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
@(vic+$20) = 6 ; you can also use expressions to 'calculate' the address @(vic+$20) = 6 ; you can also use expressions to 'calculate' the address
This is the official syntax to 'dereference a pointer' as it is often named in other languages.
You can actually also use the array indexing notation for this. It will be silently converted into
the direct memory access expression as explained above. Note that this also means that unlike regular arrays,
the index is not limited to an ubyte value. You can use a full uword to index a pointer variable like this::
pointervar[999] = 0 ; set memory byte to zero at location pointervar + 999.
Converting types into other types Converting types into other types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -515,12 +522,12 @@ Use a ``when`` statement if you have a set of fixed choices that each should res
action. It is possible to combine several choices to result in the same action:: action. It is possible to combine several choices to result in the same action::
when value { when value {
4 -> c64scr.print("four") 4 -> txt.print("four")
5 -> c64scr.print("five") 5 -> txt.print("five")
10,20,30 -> { 10,20,30 -> {
c64scr.print("ten or twenty or thirty") txt.print("ten or twenty or thirty")
} }
else -> c64scr.print("don't know") else -> txt.print("don't know")
} }
The when-*value* can be any expression but the choice values have to evaluate to The when-*value* can be any expression but the choice values have to evaluate to
@ -796,6 +803,17 @@ mkword(msb, lsb)
Don't get confused by how the system actually stores this 16-bit word value in memory (which is Don't get confused by how the system actually stores this 16-bit word value in memory (which is
in little-endian format, so lsb first then msb) in little-endian format, so lsb first then msb)
peek(address)
same as @(address) - reads the byte at the given address in memory.
peekw(address)
reads the word value at the given address in memory. Word is read as usual little-endian lsb/msb byte order.
poke(address, value)
same as @(address)=value - writes the byte value at the given address in memory.
pokew(address, value)
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
rnd() rnd()
returns a pseudo-random byte from 0..255 returns a pseudo-random byte from 0..255
@ -836,6 +854,11 @@ sizeof(name)
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes). 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. Note: usually you will be interested in the number of elements in an array, use len() for that.
offsetof(membername)
Number of bytes from the start of a struct variable that this member variable is located.
For now, this only works on members of a declared struct variable and not yet on members
referenced from the struct type itself. This might be improved in a future version of the language.
swap(x, y) swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way. Swap the values of numerical variables (or memory locations) x and y in a fast way.

View File

@ -70,7 +70,7 @@ Directives
It's not possible to return cleanly to BASIC when the program exits. The only choice is It's not possible to return cleanly to BASIC when the program exits. The only choice is
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this) to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
- style ``floatsafe`` -- like the previous one but also reserves the addresses that - style ``floatsafe`` -- like the previous one but also reserves the addresses that
are required to perform floating point operations (from the BASIC kernel). No clean exit is possible. are required to perform floating point operations (from the BASIC kernal). No clean exit is possible.
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't - style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
touch change anything else. This allows full use of BASIC and KERNAL ROM routines including default IRQs touch change anything else. This allows full use of BASIC and KERNAL ROM routines including default IRQs
during normal system operation. during normal system operation.
@ -120,17 +120,18 @@ Directives
Level: module, block. Level: module, block.
Sets special compiler options. Sets special compiler options.
- For a module option, there is ``enable_floats``, which will tell the compiler - ``enable_floats`` (module level) tells the compiler
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal). to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
Otherwise, floating point support is not enabled. Normally you don't have to use this yourself as Otherwise, floating point support is not enabled. Normally you don't have to use this yourself as
importing the ``floats`` library is required anyway and that will enable it for you automatically. importing the ``floats`` library is required anyway and that will enable it for you automatically.
- There's also ``no_sysinit`` which cause the resulting program to *not* include - ``no_sysinit`` (module level) which cause the resulting program to *not* include
the system re-initialization logic of clearing the screen, resetting I/O config etc. You'll have to the system re-initialization logic of clearing the screen, resetting I/O config etc. You'll have to
take care of that yourself. The program will just start running from whatever state the machine is in when the take care of that yourself. The program will just start running from whatever state the machine is in when the
program was launched. program was launched.
- When used in a block with the ``force_output`` option, it will force the block to be outputted - ``force_output`` (in a block) will force the block to be outputted in the final program.
in the final program. Can be useful to make sure some Can be useful to make sure some data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
data is generated that would otherwise be discarded because it's not referenced (such as sprite data). - ``align_word`` (in a block) will make the assembler align the start address of this block on a word boundary in memory (so, an even memory address).
- ``align_page`` (in a block) will make the assembler align the start address of this block on a page boundary in memory (so, the LSB of the address is 0).
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]] .. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
@ -335,6 +336,10 @@ directly access the memory. Enclose a numeric expression or literal with ``@(...
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0") @($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
@(vic+$20) = 6 ; a dynamic expression to 'calculate' the address @(vic+$20) = 6 ; a dynamic expression to 'calculate' the address
The array indexing notation is syntactic sugar for such a direct memory access expression::
pointervar[999] = 0 ; equivalent to @(pointervar+999) = 0
Constants Constants
^^^^^^^^^ ^^^^^^^^^
@ -509,7 +514,7 @@ Multiple return values
^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
Normal subroutines can only return zero or one return values. Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
(referencing a routine in kernel ROM) can return more than one return value. (referencing a routine in kernal ROM) can return more than one return value.
For example a status in the carry bit and a number in A, or a 16-bit value in A/Y registers. For example a status in the carry bit and a number in A, or a 16-bit value in A/Y registers.
It is not possible to process the results of a call to these kind of routines It is not possible to process the results of a call to these kind of routines
directly from the language, because only single value assignments are possible. directly from the language, because only single value assignments are possible.
@ -768,11 +773,11 @@ Choices can result in a single statement or a block of multiple statements in w
case you have to use { } to enclose them:: case you have to use { } to enclose them::
when value { when value {
4 -> c64scr.print("four") 4 -> txt.print("four")
5 -> c64scr.print("five") 5 -> txt.print("five")
10,20,30 -> { 10,20,30 -> {
c64scr.print("ten or twenty or thirty") txt.print("ten or twenty or thirty")
} }
else -> c64scr.print("don't know") else -> txt.print("don't know")
} }

View File

@ -17,7 +17,7 @@ Currently there are two machines that are supported as compiler target (selectab
This chapter explains the relevant system details of these machines. This chapter explains the relevant system details of these machines.
.. hint:: .. hint::
If you only use standard kernel and prog8 library routines, If you only use standard kernal and prog8 library routines,
it is possible to compile the *exact same program* for both machines (just change the compiler target flag)! it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
@ -139,26 +139,17 @@ IRQ Handling
============ ============
Normally, the system's default IRQ handling is not interfered with. Normally, the system's default IRQ handling is not interfered with.
You can however install your own IRQ handler. You can however install your own IRQ handler (for clean separation, it is advised to define it inside its own block).
This is possible ofcourse by doing it all using customized inline assembly, There are a few library routines available to make setting up C-64 60hz IRQs and Raster IRQs a lot easier (no assembly code required).
but there are a few library routines available to make setting up C-64 IRQs and raster IRQs a lot easier (no assembly code required).
For the C64 these routines are:: For the C64 these routines are::
c64.set_irqvec() c64.set_irq(uword handler_address, boolean useKernal)
c64.set_irqvec_excl() c64.set_rasterirq(uword handler_address, uword rasterline, boolean useKernal)
c64.restore_irq() ; set everything back to the systems default irq handler
c64.set_rasterirq( <raster line> ) And for the Commander X16:
c64.set_rasterirq_excl( <raster line> )
c64.restore_irqvec() ; set it back to the systems default irq handler
If you activate an IRQ handler with one of these, it expects the handler to be defined
as a subroutine ``irq`` in the module ``irq`` so like this::
irq {
sub irq() {
; ... irq handling here ...
}
}
cx16.set_irq(uword handler_address, boolean useKernal) ; vsync irq
cx16.set_rasterirq(uword handler_address, uword rasterline) ; note: disables kernal irq handler! sys.wait() won't work anymore
cx16.restore_irq() ; set everything back to the systems default irq handler

View File

@ -50,7 +50,7 @@ Calling the routine is just a simple JSR instruction, but the other two work lik
``asmsub`` routines ``asmsub`` routines
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
These are usually declarations of kernel (ROM) routines or low-level assembly only routines, These are usually declarations of kernal (ROM) routines or low-level assembly only routines,
that have their arguments solely passed into specific registers. that have their arguments solely passed into specific registers.
Sometimes even via a processor status flag such as the Carry flag. Sometimes even via a processor status flag such as the Carry flag.
Return values also via designated registers. Return values also via designated registers.

View File

@ -2,15 +2,21 @@
TODO TODO
==== ====
- use (zp) addressing mode on 65c02 specific code rather than ldy#0 / lda (zp),y - optimize assigning array and struct variables (multi-element assings -> memcopy)
- optimize pointer access code @(pointer)? use a subroutine? macro? 65c02 vs 6502?
- can we get rid of the --longOptionName command line options and only keep the short versions? https://github.com/Kotlin/kotlinx-cli/issues/50
- add a compiler option to generate a symbol listing at the end
- optimizer: detect variables that are written but never read - mark those as unused too and remove them, such as uword unused = memory("unused222", 20) - also remove the memory slab allocation
- hoist all variable declarations up to the subroutine scope *before* even the constant folding takes place (to avoid undefined symbol errors when referring to a variable from another nested scope in the subroutine) - hoist all variable declarations up to the subroutine scope *before* even the constant folding takes place (to avoid undefined symbol errors when referring to a variable from another nested scope in the subroutine)
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_' - optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+1), @(ptr+2))
- optimize several inner loops in gfx2
- try to fix the bresenham line routines in graphics and gfx2 (sometimes they're a pixel 'off')
- add modes 2 and 3 to gfx2 (lowres 4 color and 16 color)
- add a flood fill routine to gfx2?
- add a f_seek() routine for the Cx16 that uses its seek dos api?
- refactor the asmgen into their own submodule?
- refactor the compiler optimizers into their own submodule?
- optimizer: detect variables that are written but never read - mark those as unused too and remove them, such as uword unused = memory("unused222", 20) - also remove the memory slab allocation
- add a compiler option to not remove unused subroutines. this allows for building library programs
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``v_``
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging) - option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
- c64: use VIC banking to move up the graphics bitmap memory location. Move it to $e000 under the kernal rom? - c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking)
- some support for recursive subroutines? - some support for recursive subroutines?
- via %option recursive?: allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters - via %option recursive?: allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
- Or via a special recursive call operation that copies the current values of all local vars (including arguments) to the stack, replaces the arguments, jsr subroutine, and after returning copy the stack back to the local variables - Or via a special recursive call operation that copies the current values of all local vars (including arguments) to the stack, replaces the arguments, jsr subroutine, and after returning copy the stack back to the local variables
@ -22,30 +28,12 @@ More optimizations
Add more compiler optimizations to the existing ones. Add more compiler optimizations to the existing ones.
- further optimize assignment codegeneration, such as the following: - further optimize assignment codegeneration, such as the following:
- rewrite expression code generator to not use eval stack but a fixed number of predetermined value 'variables' (1 per nesting level?)
- binexpr splitting (beware self-referencing expressions and asm code ballooning though) - binexpr splitting (beware self-referencing expressions and asm code ballooning though)
- more optimizations on the language AST level - more optimizations on the language AST level
- more optimizations on the final assembly source level - more optimizations on the final assembly source level
Eval stack redesign? (lot of work)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The eval stack is now a split lsb/msb stack using X as the stackpointer.
Is it easier/faster to just use a single page unsplit stack?
It could then even be moved into the zeropage to reduce code size and slowness.
Or just move the LSB portion into a slab of the zeropage.
Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly
without having to index with X into the eval stack all the time?
This could GREATLY improve code size and speed for operations that work on just a single value.
Bug Fixing
^^^^^^^^^^
Ofcourse there are always bugs to fix ;)
Misc Misc
^^^^ ^^^^

144
examples/animals.p8 Normal file
View File

@ -0,0 +1,144 @@
%import textio
%import string
; Animal guessing game where the computer gets smarter every time.
; Note: this program is compatible with C64 and CX16.
main {
const ubyte database_size = 100
uword animal_names_buf
uword questions_buf
uword animal_names_ptr
uword questions_ptr
uword[database_size] animals
uword[database_size] questions
uword[database_size] answers_questions
uword[database_size] answers_animals
ubyte new_animal_number
ubyte new_question_number
str userinput = "x"*80
sub start() {
; initialize the database
animal_names_buf = memory("animalnames", 500)
questions_buf = memory("questions", 2000)
animal_names_ptr = animal_names_buf
questions_ptr = questions_buf
animals[0] = 0
animals[1] = "dolphin"
animals[2] = "eagle"
animals[3] = "horse"
new_animal_number = 4
questions[0] = 0
questions[1] = "does it swim"
questions[2] = "can it fly"
new_question_number = 3
answers_questions[0] = mkword(0, 0)
answers_questions[1] = mkword(0, 2)
answers_questions[2] = mkword(0, 0)
answers_animals[0] = mkword(0, 0)
answers_animals[1] = mkword(1, 0)
answers_animals[2] = mkword(2, 3)
; play the game
game()
}
sub game() {
repeat {
ubyte current_question = 1
txt.print("\n\nanimal guessing game!\nthink of an animal.\n")
ubyte guessed = false
while not guessed {
txt.print(questions[current_question])
txt.print("? ")
if txt.input_chars(userinput) {
txt.nl()
ubyte animal_number
if userinput[0]=='y' {
animal_number = msb(answers_animals[current_question])
if animal_number {
guess(current_question, true, animal_number)
guessed = true
} else {
current_question = msb(answers_questions[current_question])
}
}
else if userinput[0]=='n' {
animal_number = lsb(answers_animals[current_question])
if animal_number {
guess(current_question, false, animal_number)
guessed = true
} else {
current_question = lsb(answers_questions[current_question])
}
}
else {
txt.print("answer (y)es or (n)o please.\n")
}
} else
txt.nl()
}
}
}
sub guess(ubyte question_number, ubyte given_answer_yesno, ubyte animal_number) {
txt.print("is it a ")
txt.print(animals[animal_number])
txt.print("? ")
txt.input_chars(userinput)
if userinput[0] == 'y' {
txt.print("\n\nsee, i knew it!\n")
return
}
str name = "x"*30
txt.print("\n\ni give up. what is it? ")
txt.input_chars(name)
txt.print("\nwhat yes-no question would best articulate the difference\nbetween a ")
txt.print(animals[animal_number])
txt.print(" and a ")
txt.print(name)
txt.print("? ")
txt.input_chars(userinput)
txt.print("\nfor a ")
txt.print(name)
txt.print(", what is the answer to that; yes or no? ")
str answer = "x"*10
txt.input_chars(answer)
animals[new_animal_number] = animal_names_ptr
questions[new_question_number] = questions_ptr
animal_names_ptr += string.copy(name, animal_names_ptr)+1 ; store animal name in buffer
questions_ptr += string.copy(userinput, questions_ptr)+1 ; store question in buffer
answers_questions[new_question_number] = mkword(0, 0)
if answer[0]=='y'
answers_animals[new_question_number] = mkword(new_animal_number, animal_number)
else
answers_animals[new_question_number] = mkword(animal_number, new_animal_number)
uword previous_animals = answers_animals[question_number]
uword previous_questions = answers_questions[question_number]
if given_answer_yesno {
answers_animals[question_number] = mkword(0, lsb(previous_animals))
answers_questions[question_number] = mkword(new_question_number, lsb(previous_questions))
} else {
answers_animals[question_number] = mkword(msb(previous_animals), 0)
answers_questions[question_number] = mkword(msb(previous_questions), new_question_number)
}
new_animal_number++
new_question_number++
txt.print("\n\nthanks, i know more animals now! let's try again.\n")
}
}

View File

@ -1,496 +0,0 @@
from collections import Counter
from enum import IntEnum
class AddrMode(IntEnum):
Imp = 1,
Acc = 2,
Imm = 3,
Zp = 4,
ZpX = 5,
ZpY = 6,
Rel = 7,
Abs = 8,
AbsX = 9,
AbsY = 10,
Ind = 11,
IzX = 12,
IzY = 13,
Zpr = 14,
Izp = 15,
IaX = 16
AllInstructions = [
(0x00, "brk", AddrMode.Imp),
(0x01, "ora", AddrMode.IzX),
(0x02, "nop", AddrMode.Imm),
(0x03, "nop", AddrMode.Imp),
(0x04, "tsb", AddrMode.Zp),
(0x05, "ora", AddrMode.Zp),
(0x06, "asl", AddrMode.Zp),
(0x07, "rmb0", AddrMode.Zp),
(0x08, "php", AddrMode.Imp),
(0x09, "ora", AddrMode.Imm),
(0x0a, "asl", AddrMode.Acc),
(0x0b, "nop", AddrMode.Imp),
(0x0c, "tsb", AddrMode.Abs),
(0x0d, "ora", AddrMode.Abs),
(0x0e, "asl", AddrMode.Abs),
(0x0f, "bbr0", AddrMode.Zpr),
(0x10, "bpl", AddrMode.Rel),
(0x11, "ora", AddrMode.IzY),
(0x12, "ora", AddrMode.Izp),
(0x13, "nop", AddrMode.Imp),
(0x14, "trb", AddrMode.Zp),
(0x15, "ora", AddrMode.ZpX),
(0x16, "asl", AddrMode.ZpX),
(0x17, "rmb1", AddrMode.Zp),
(0x18, "clc", AddrMode.Imp),
(0x19, "ora", AddrMode.AbsY),
(0x1a, "inc", AddrMode.Acc),
(0x1b, "nop", AddrMode.Imp),
(0x1c, "trb", AddrMode.Abs),
(0x1d, "ora", AddrMode.AbsX),
(0x1e, "asl", AddrMode.AbsX),
(0x1f, "bbr1", AddrMode.Zpr),
(0x20, "jsr", AddrMode.Abs),
(0x21, "and", AddrMode.IzX),
(0x22, "nop", AddrMode.Imm),
(0x23, "nop", AddrMode.Imp),
(0x24, "bit", AddrMode.Zp),
(0x25, "and", AddrMode.Zp),
(0x26, "rol", AddrMode.Zp),
(0x27, "rmb2", AddrMode.Zp),
(0x28, "plp", AddrMode.Imp),
(0x29, "and", AddrMode.Imm),
(0x2a, "rol", AddrMode.Acc),
(0x2b, "nop", AddrMode.Imp),
(0x2c, "bit", AddrMode.Abs),
(0x2d, "and", AddrMode.Abs),
(0x2e, "rol", AddrMode.Abs),
(0x2f, "bbr2", AddrMode.Zpr),
(0x30, "bmi", AddrMode.Rel),
(0x31, "and", AddrMode.IzY),
(0x32, "and", AddrMode.Izp),
(0x33, "nop", AddrMode.Imp),
(0x34, "bit", AddrMode.ZpX),
(0x35, "and", AddrMode.ZpX),
(0x36, "rol", AddrMode.ZpX),
(0x37, "rmb3", AddrMode.Zp),
(0x38, "sec", AddrMode.Imp),
(0x39, "and", AddrMode.AbsY),
(0x3a, "dec", AddrMode.Acc),
(0x3b, "nop", AddrMode.Imp),
(0x3c, "bit", AddrMode.AbsX),
(0x3d, "and", AddrMode.AbsX),
(0x3e, "rol", AddrMode.AbsX),
(0x3f, "bbr3", AddrMode.Zpr),
(0x40, "rti", AddrMode.Imp),
(0x41, "eor", AddrMode.IzX),
(0x42, "nop", AddrMode.Imm),
(0x43, "nop", AddrMode.Imp),
(0x44, "nop", AddrMode.Zp),
(0x45, "eor", AddrMode.Zp),
(0x46, "lsr", AddrMode.Zp),
(0x47, "rmb4", AddrMode.Zp),
(0x48, "pha", AddrMode.Imp),
(0x49, "eor", AddrMode.Imm),
(0x4a, "lsr", AddrMode.Acc),
(0x4b, "nop", AddrMode.Imp),
(0x4c, "jmp", AddrMode.Abs),
(0x4d, "eor", AddrMode.Abs),
(0x4e, "lsr", AddrMode.Abs),
(0x4f, "bbr4", AddrMode.Zpr),
(0x50, "bvc", AddrMode.Rel),
(0x51, "eor", AddrMode.IzY),
(0x52, "eor", AddrMode.Izp),
(0x53, "nop", AddrMode.Imp),
(0x54, "nop", AddrMode.ZpX),
(0x55, "eor", AddrMode.ZpX),
(0x56, "lsr", AddrMode.ZpX),
(0x57, "rmb5", AddrMode.Zp),
(0x58, "cli", AddrMode.Imp),
(0x59, "eor", AddrMode.AbsY),
(0x5a, "phy", AddrMode.Imp),
(0x5b, "nop", AddrMode.Imp),
(0x5c, "nop", AddrMode.Abs),
(0x5d, "eor", AddrMode.AbsX),
(0x5e, "lsr", AddrMode.AbsX),
(0x5f, "bbr5", AddrMode.Zpr),
(0x60, "rts", AddrMode.Imp),
(0x61, "adc", AddrMode.IzX),
(0x62, "nop", AddrMode.Imm),
(0x63, "nop", AddrMode.Imp),
(0x64, "stz", AddrMode.Zp),
(0x65, "adc", AddrMode.Zp),
(0x66, "ror", AddrMode.Zp),
(0x67, "rmb6", AddrMode.Zp),
(0x68, "pla", AddrMode.Imp),
(0x69, "adc", AddrMode.Imm),
(0x6a, "ror", AddrMode.Acc),
(0x6b, "nop", AddrMode.Imp),
(0x6c, "jmp", AddrMode.Ind),
(0x6d, "adc", AddrMode.Abs),
(0x6e, "ror", AddrMode.Abs),
(0x6f, "bbr6", AddrMode.Zpr),
(0x70, "bvs", AddrMode.Rel),
(0x71, "adc", AddrMode.IzY),
(0x72, "adc", AddrMode.Izp),
(0x73, "nop", AddrMode.Imp),
(0x74, "stz", AddrMode.ZpX),
(0x75, "adc", AddrMode.ZpX),
(0x76, "ror", AddrMode.ZpX),
(0x77, "rmb7", AddrMode.Zp),
(0x78, "sei", AddrMode.Imp),
(0x79, "adc", AddrMode.AbsY),
(0x7a, "ply", AddrMode.Imp),
(0x7b, "nop", AddrMode.Imp),
(0x7c, "jmp", AddrMode.IaX),
(0x7d, "adc", AddrMode.AbsX),
(0x7e, "ror", AddrMode.AbsX),
(0x7f, "bbr7", AddrMode.Zpr),
(0x80, "bra", AddrMode.Rel),
(0x81, "sta", AddrMode.IzX),
(0x82, "nop", AddrMode.Imm),
(0x83, "nop", AddrMode.Imp),
(0x84, "sty", AddrMode.Zp),
(0x85, "sta", AddrMode.Zp),
(0x86, "stx", AddrMode.Zp),
(0x87, "smb0", AddrMode.Zp),
(0x88, "dey", AddrMode.Imp),
(0x89, "bit", AddrMode.Imm),
(0x8a, "txa", AddrMode.Imp),
(0x8b, "nop", AddrMode.Imp),
(0x8c, "sty", AddrMode.Abs),
(0x8d, "sta", AddrMode.Abs),
(0x8e, "stx", AddrMode.Abs),
(0x8f, "bbs0", AddrMode.Zpr),
(0x90, "bcc", AddrMode.Rel),
(0x91, "sta", AddrMode.IzY),
(0x92, "sta", AddrMode.Izp),
(0x93, "nop", AddrMode.Imp),
(0x94, "sty", AddrMode.ZpX),
(0x95, "sta", AddrMode.ZpX),
(0x96, "stx", AddrMode.ZpY),
(0x97, "smb1", AddrMode.Zp),
(0x98, "tya", AddrMode.Imp),
(0x99, "sta", AddrMode.AbsY),
(0x9a, "txs", AddrMode.Imp),
(0x9b, "nop", AddrMode.Imp),
(0x9c, "stz", AddrMode.Abs),
(0x9d, "sta", AddrMode.AbsX),
(0x9e, "stz", AddrMode.AbsX),
(0x9f, "bbs1", AddrMode.Zpr),
(0xa0, "ldy", AddrMode.Imm),
(0xa1, "lda", AddrMode.IzX),
(0xa2, "ldx", AddrMode.Imm),
(0xa3, "nop", AddrMode.Imp),
(0xa4, "ldy", AddrMode.Zp),
(0xa5, "lda", AddrMode.Zp),
(0xa6, "ldx", AddrMode.Zp),
(0xa7, "smb2", AddrMode.Zp),
(0xa8, "tay", AddrMode.Imp),
(0xa9, "lda", AddrMode.Imm),
(0xaa, "tax", AddrMode.Imp),
(0xab, "nop", AddrMode.Imp),
(0xac, "ldy", AddrMode.Abs),
(0xad, "lda", AddrMode.Abs),
(0xae, "ldx", AddrMode.Abs),
(0xaf, "bbs2", AddrMode.Zpr),
(0xb0, "bcs", AddrMode.Rel),
(0xb1, "lda", AddrMode.IzY),
(0xb2, "lda", AddrMode.Izp),
(0xb3, "nop", AddrMode.Imp),
(0xb4, "ldy", AddrMode.ZpX),
(0xb5, "lda", AddrMode.ZpX),
(0xb6, "ldx", AddrMode.ZpY),
(0xb7, "smb3", AddrMode.Zp),
(0xb8, "clv", AddrMode.Imp),
(0xb9, "lda", AddrMode.AbsY),
(0xba, "tsx", AddrMode.Imp),
(0xbb, "nop", AddrMode.Imp),
(0xbc, "ldy", AddrMode.AbsX),
(0xbd, "lda", AddrMode.AbsX),
(0xbe, "ldx", AddrMode.AbsY),
(0xbf, "bbs3", AddrMode.Zpr),
(0xc0, "cpy", AddrMode.Imm),
(0xc1, "cmp", AddrMode.IzX),
(0xc2, "nop", AddrMode.Imm),
(0xc3, "nop", AddrMode.Imp),
(0xc4, "cpy", AddrMode.Zp),
(0xc5, "cmp", AddrMode.Zp),
(0xc6, "dec", AddrMode.Zp),
(0xc7, "smb4", AddrMode.Zp),
(0xc8, "iny", AddrMode.Imp),
(0xc9, "cmp", AddrMode.Imm),
(0xca, "dex", AddrMode.Imp),
(0xcb, "wai", AddrMode.Imp),
(0xcc, "cpy", AddrMode.Abs),
(0xcd, "cmp", AddrMode.Abs),
(0xce, "dec", AddrMode.Abs),
(0xcf, "bbs4", AddrMode.Zpr),
(0xd0, "bne", AddrMode.Rel),
(0xd1, "cmp", AddrMode.IzY),
(0xd2, "cmp", AddrMode.Izp),
(0xd3, "nop", AddrMode.Imp),
(0xd4, "nop", AddrMode.ZpX),
(0xd5, "cmp", AddrMode.ZpX),
(0xd6, "dec", AddrMode.ZpX),
(0xd7, "smb5", AddrMode.Zp),
(0xd8, "cld", AddrMode.Imp),
(0xd9, "cmp", AddrMode.AbsY),
(0xda, "phx", AddrMode.Imp),
(0xdb, "stp", AddrMode.Imp),
(0xdc, "nop", AddrMode.Abs),
(0xdd, "cmp", AddrMode.AbsX),
(0xde, "dec", AddrMode.AbsX),
(0xdf, "bbs5", AddrMode.Zpr),
(0xe0, "cpx", AddrMode.Imm),
(0xe1, "sbc", AddrMode.IzX),
(0xe2, "nop", AddrMode.Imm),
(0xe3, "nop", AddrMode.Imp),
(0xe4, "cpx", AddrMode.Zp),
(0xe5, "sbc", AddrMode.Zp),
(0xe6, "inc", AddrMode.Zp),
(0xe7, "smb6", AddrMode.Zp),
(0xe8, "inx", AddrMode.Imp),
(0xe9, "sbc", AddrMode.Imm),
(0xea, "nop", AddrMode.Imp),
(0xeb, "nop", AddrMode.Imp),
(0xec, "cpx", AddrMode.Abs),
(0xed, "sbc", AddrMode.Abs),
(0xee, "inc", AddrMode.Abs),
(0xef, "bbs6", AddrMode.Zpr),
(0xf0, "beq", AddrMode.Rel),
(0xf1, "sbc", AddrMode.IzY),
(0xf2, "sbc", AddrMode.Izp),
(0xf3, "nop", AddrMode.Imp),
(0xf4, "nop", AddrMode.ZpX),
(0xf5, "sbc", AddrMode.ZpX),
(0xf6, "inc", AddrMode.ZpX),
(0xf7, "smb7", AddrMode.Zp),
(0xf8, "sed", AddrMode.Imp),
(0xf9, "sbc", AddrMode.AbsY),
(0xfa, "plx", AddrMode.Imp),
(0xfb, "nop", AddrMode.Imp),
(0xfc, "nop", AddrMode.AbsX),
(0xfd, "sbc", AddrMode.AbsX),
(0xfe, "inc", AddrMode.AbsX),
(0xff, "bbs7", AddrMode.Zpr)
]
# NOP is weird, it is all over the place.
# For the 'common' immediate NOP, keep only the $EA opcode (this was the original NOP on the 6502)
Instructions = [ins for ins in AllInstructions if ins[1] != "nop"] + [(0xea, "nop", AddrMode.Imp)]
InstructionsByName = {}
for ins in Instructions:
if ins[1] not in InstructionsByName:
InstructionsByName[ins[1]] = {ins[2]: ins[0]}
else:
InstructionsByName[ins[1]][ins[2]] = ins[0]
InstructionsByMode = {}
for ins in Instructions:
if ins[2] not in InstructionsByMode:
InstructionsByMode[ins[2]] = [(ins[1], ins[0])]
else:
InstructionsByMode[ins[2]].append((ins[1], ins[0]))
# build the name->modes table
print("; generated by opcodes.py")
print("; addressing modes:")
for mode in AddrMode:
print(";", mode.value, "=", mode.name)
print()
print("""
.enc "petscii" ;define an ascii to petscii encoding
.cdef " @", 32 ;characters
.cdef "AZ", $c1
.cdef "az", $41
.cdef "[[", $5b
.cdef "]]", $5d
.edef "<nothing>", [];replace with no bytes
""")
for instr in sorted(InstructionsByName.items()):
print("i_" + instr[0] + ":\n\t.byte ", end="")
if len(instr[1]) == 1:
# many instructions have just 1 addressing mode, save space for those
info = instr[1].popitem()
print("1,", info[0].value,",", info[1])
else:
print("0, ", end='')
mode_opcodes = []
for mode in AddrMode:
if mode in instr[1]:
mode_opcodes.append(instr[1][mode])
else:
mode_opcodes.append(0)
print(",".join(str(o) for o in mode_opcodes), end="")
print()
def determine_mnemonics():
mnemonics = list(sorted(set(ins[1] for ins in Instructions)))
# opcodes histogram (ordered by occurrence) (in kernal + basic roms of the c64):
opcode_occurrences = [
(32, 839), (133, 502), (165, 488), (0, 429), (208, 426), (169, 390), (76, 324), (240, 322), (2, 314), (160, 245),
(96, 228), (3, 201), (1, 191), (255, 186), (144, 182), (170, 175), (162, 169), (177, 165), (104, 159), (164, 158),
(132, 157), (201, 156), (72, 151), (141, 150), (200, 146), (173, 144), (166, 139), (176, 139), (16, 138),
(134, 138), (73, 127), (24, 119), (101, 113), (69, 109), (13, 107), (34, 104), (145, 103), (4, 102), (168, 101),
(221, 98), (230, 93), (48, 91), (189, 87), (41, 86), (6, 86), (9, 86), (8, 85), (79, 85), (138, 80), (10, 80),
(7, 79), (185, 77), (56, 75), (44, 75), (78, 74), (105, 73), (5, 73), (174, 73), (220, 71), (198, 69), (232, 69),
(36, 69), (202, 67), (152, 67), (95, 67), (100, 65), (102, 65), (247, 65), (188, 64), (136, 64), (84, 64),
(122, 62), (128, 61), (80, 61), (186, 60), (82, 59), (97, 58), (15, 57), (70, 57), (229, 56), (19, 55), (40, 54),
(183, 54), (65, 54), (233, 53), (180, 53), (12, 53), (171, 53), (197, 53), (83, 52), (248, 52), (112, 51),
(237, 51), (89, 50), (11, 50), (158, 50), (74, 49), (224, 48), (20, 47), (238, 47), (108, 46), (234, 46),
(251, 46), (254, 46), (184, 45), (14, 44), (163, 44), (226, 43), (211, 43), (88, 43), (98, 42), (17, 42),
(153, 42), (243, 41), (228, 41), (99, 41), (253, 41), (209, 41), (187, 39), (123, 39), (67, 39), (196, 38),
(68, 38), (35, 38), (172, 38), (175, 38), (161, 38), (85, 38), (191, 37), (113, 37), (182, 37), (151, 37),
(71, 36), (181, 35), (214, 35), (121, 35), (157, 35), (178, 35), (77, 35), (42, 34), (212, 33), (18, 33),
(127, 33), (241, 33), (21, 33), (249, 32), (23, 31), (245, 30), (142, 30), (55, 29), (140, 29), (46, 29),
(192, 29), (179, 29), (252, 29), (115, 29), (22, 29), (43, 28), (215, 28), (45, 28), (246, 28), (38, 28),
(86, 27), (225, 27), (25, 26), (239, 26), (58, 26), (167, 26), (147, 26), (217, 26), (149, 25), (30, 25),
(206, 25), (28, 24), (47, 24), (37, 24), (155, 24), (129, 23), (148, 23), (111, 23), (29, 23), (39, 23),
(51, 22), (193, 22), (236, 22), (120, 22), (64, 22), (204, 21), (210, 21), (244, 21), (52, 21), (66, 21),
(114, 20), (250, 20), (106, 20), (93, 19), (199, 19), (218, 19), (154, 19), (205, 19), (50, 19), (159, 19),
(194, 19), (49, 19), (190, 19), (103, 18), (216, 18), (213, 18), (107, 18), (131, 18), (63, 18), (94, 18),
(91, 17), (242, 17), (109, 17), (53, 16), (227, 16), (139, 16), (31, 16), (75, 16), (60, 16), (195, 15),
(231, 15), (62, 15), (59, 15), (87, 14), (207, 14), (27, 14), (90, 14), (110, 13), (223, 13), (57, 13),
(118, 12), (26, 12), (203, 12), (81, 12), (156, 12), (54, 12), (235, 12), (146, 11), (135, 11), (126, 11),
(150, 11), (130, 11), (143, 10), (61, 10), (219, 10), (124, 9), (222, 9), (125, 9), (119, 7), (137, 7),
(33, 7), (117, 5), (92, 4), (116, 3)
]
cnt = Counter()
for opcode, amount in opcode_occurrences:
cnt[AllInstructions[opcode][1]] += amount
cnt["nop"] = 13
cnt["tsb"] = 13
four_letter_mnemonics = list(sorted([ins[1] for ins in AllInstructions if len(ins[1])>3]))
for ins4 in four_letter_mnemonics:
del cnt[ins4]
cnt[ins4] = 1
mnem2 = [c[0] for c in cnt.most_common()]
if len(mnem2)!=len(mnemonics):
raise ValueError("mnem count mismatch")
return mnem2
mnemonics = determine_mnemonics()
def first_letters():
firstletters = {m[0]: 0 for m in mnemonics}
return firstletters.keys()
def second_letters(firstletter):
secondletters = {m[1]: 0 for m in mnemonics if m[0] == firstletter}
return secondletters.keys()
def third_letters(firstletter, secondletter):
thirdletters = {m[2]: 0 for m in mnemonics if m[0] == firstletter and m[1] == secondletter}
return thirdletters.keys()
def fourth_letters(firstletter, secondletter, thirdletter):
longmnem = [m for m in mnemonics if len(m) > 3]
fourthletters = {m[3]: 0 for m in longmnem if m[0] == firstletter and m[1] == secondletter and m[2] == thirdletter}
return fourthletters.keys()
def make_tree():
tree = {}
for first in first_letters():
tree[first] = {
secondletter: {
thirdletter: {
fourthletter: {}
for fourthletter in fourth_letters(first, secondletter, thirdletter)
}
for thirdletter in third_letters(first, secondletter)
}
for secondletter in second_letters(first)
}
return tree
tree = make_tree()
print("get_opcode_info .proc")
print("_mnem_fourth_letter = cx16.r4")
print("_mnem_fifth_letter = cx16.r5")
for first in tree:
print(" cmp #'%s'" % first)
print(" bne _not_%s" % first)
for second in tree[first]:
print(" cpx #'%s'" % second)
print(" bne _not_%s%s" % (first,second))
for third in tree[first][second]:
print(" cpy #'%s'" % third)
print(" bne _not_%s%s%s" % (first, second, third))
fourth = tree[first][second][third]
if fourth:
if "".join(fourth.keys()) != "01234567":
raise ValueError("fourth", fourth.keys())
print(" bra _check_%s%s%s" % (first, second, third))
else:
print(" lda _mnem_fourth_letter") # check that the fourth letter is not present
print(" bne _invalid")
print(" lda #<i_%s%s%s" % (first, second, third))
print(" ldy #>i_%s%s%s" % (first, second, third))
print(" rts")
print("_not_%s%s%s:" % (first, second, third))
print("_not_%s%s:" % (first, second))
print("_not_%s:" % first)
print("_invalid:")
print(" lda #0")
print(" ldy #0")
print(" rts")
# the 4-letter mnemonics are:
# smb[0-7]
# bbr[0-7]
# rmb[0-7]
# bbs[0-7]
for fourlettermnemonic in ["smb", "bbr", "rmb", "bbs"]:
print("_check_%s" % fourlettermnemonic)
print(" lda #<_tab_%s" % fourlettermnemonic)
print(" ldy #>_tab_%s" % fourlettermnemonic)
print(""" sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
bra _check4""")
print("""_check4
lda _mnem_fourth_letter
cmp #'0'
bcc _invalid
cmp #'8'
bcs _invalid
lda _mnem_fifth_letter ; must have no fifth letter
bne _invalid
tay
lda (P8ZP_SCRATCH_W2),y
pha
iny
lda (P8ZP_SCRATCH_W2),y
tay
pla
rts""")
for fourlettermnemonic in ["smb", "bbr", "rmb", "bbs"]:
print("_tab_%s" % fourlettermnemonic)
for ii in "01234567":
print(" .word i_%s%s" % (fourlettermnemonic, ii))
print(" .pend")

View File

@ -23,7 +23,7 @@ main {
c64.SCROLX &= %11110111 ; 38 column mode c64.SCROLX &= %11110111 ; 38 column mode
c64.set_rasterirq(200) ; enable animation c64.set_rasterirq(&irq.irq, 200, false) ; enable animation via raster interrupt
ubyte target_height = 10 ubyte target_height = 10
ubyte active_height = 24 ubyte active_height = 24

View File

@ -7,7 +7,7 @@ main {
sub start() { sub start() {
txt.print("playing the music from boulderdash,\nmade in 1984 by peter liepa.\n\n") txt.print("playing the music from boulderdash,\nmade in 1984 by peter liepa.\n\n")
c64.set_rasterirq(60) ; enable raster irq c64.set_rasterirq(&irq.irq, 60, true) ; enable playback via raster irq
} }
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More