mirror of
https://github.com/irmen/prog8.git
synced 2025-06-17 16:23:42 +00:00
Compare commits
189 Commits
Author | SHA1 | Date | |
---|---|---|---|
e680de05ea | |||
56fec674c5 | |||
54d92a027a | |||
319ac3a641 | |||
0a03c46351 | |||
ae1b62e147 | |||
8d567f6b06 | |||
b1ef09675b | |||
2b7b925090 | |||
e0454e95db | |||
91e421d961 | |||
c853afe769 | |||
1a64cb38d5 | |||
ccebd22856 | |||
a1f3b82333 | |||
3dda29781e | |||
a9d297ee31 | |||
e5ff61f201 | |||
d116eb7655 | |||
bc726c6334 | |||
123473dfc8 | |||
d9eccd4fba | |||
5b890847e5 | |||
64c85b9617 | |||
3e3b0bcd8b | |||
4c1eb1b12a | |||
530d03d284 | |||
619fa9b65e | |||
0032235933 | |||
61d1f1ea87 | |||
238d27acdc | |||
2f62271453 | |||
75d5117a2d | |||
b4700af2f5 | |||
374e2b311d | |||
49036abbaf | |||
38ccbac97c | |||
6b4896b8f5 | |||
d582d1cc42 | |||
9e2b8a2aa9 | |||
6fdc733941 | |||
422b390c48 | |||
67a9d1285c | |||
8e26e38ecc | |||
02e12d8575 | |||
fe2954ce08 | |||
1fe4439395 | |||
2ff04d2abd | |||
3f30d3aa89 | |||
129e17b33a | |||
bf2d8c3f4b | |||
b29f04ce01 | |||
d185ebad48 | |||
605df7c91c | |||
ec60cad8bb | |||
6aa0f5a392 | |||
4cae2c56ec | |||
d840975054 | |||
1b14da6c03 | |||
292640b17a | |||
112a7b09f2 | |||
863ec9ce8a | |||
2eb346a205 | |||
8092355acb | |||
e7ef2ed31b | |||
af4de6d2fc | |||
69f73dd779 | |||
9706b46012 | |||
6d75dd3bb8 | |||
bd295ffc99 | |||
07ce3e3c9d | |||
cbc3e37a89 | |||
3626828ceb | |||
24b77fb5a5 | |||
1505fe686a | |||
0991131fa8 | |||
2e928bd3c2 | |||
ca868ae19e | |||
3e286dd14c | |||
11247d52b1 | |||
1dbc902513 | |||
330e691b78 | |||
6780d4f562 | |||
b30b8b7368 | |||
3df182b8c3 | |||
7f21d89fea | |||
2b267b4ba1 | |||
ef64881528 | |||
9a6bd760bd | |||
00b9766aea | |||
6381d2b6ac | |||
d2ab5f230d | |||
824b41d457 | |||
b5523c7077 | |||
eb3594b18c | |||
852d85d010 | |||
5e0aef04fe | |||
a00c693f93 | |||
c943da1448 | |||
b630fae580 | |||
38e40084f1 | |||
bf23ad78e6 | |||
ded1d19737 | |||
496a3b0d2c | |||
6922333755 | |||
a00c39e9cf | |||
1c1da8e38e | |||
50a306f492 | |||
6995ee2d17 | |||
6c60ea9cac | |||
2431ed811a | |||
6bd205c02a | |||
62ec77e148 | |||
9120e1de88 | |||
60e169bd87 | |||
e4bca5fe47 | |||
a1729b65ab | |||
2950d26c8e | |||
4f8d4a9585 | |||
d787795759 | |||
cf74e73e27 | |||
2770254fd9 | |||
de04bd8cfa | |||
076a547f91 | |||
dffd0a2706 | |||
6c66f86103 | |||
26502c949a | |||
8dfe510883 | |||
96ba9f5902 | |||
3a6ba0ab71 | |||
32d894d6b6 | |||
543efa4299 | |||
eba0708099 | |||
51e6bf0d45 | |||
07b5c44a54 | |||
9fe32c1c34 | |||
0e0278c84a | |||
dea775a9cd | |||
7e3e18a5c7 | |||
8e3ebc84f0 | |||
e6079dfd71 | |||
2b435fe6a5 | |||
4e640b11fd | |||
8b1e1e68fa | |||
fd11927708 | |||
cd500fee8c | |||
1bd32c0f19 | |||
7aefca3de0 | |||
f275ed96ea | |||
d14dac3872 | |||
b0213b0565 | |||
c677f0a875 | |||
6e65cb2c0a | |||
e65c5402d7 | |||
334f86480a | |||
0e62f5b759 | |||
edf9a500d3 | |||
001d01fdaf | |||
a95677564e | |||
4aca8bb8df | |||
5540482888 | |||
00d735249b | |||
b5289511ba | |||
b6ded8501f | |||
781915d2cf | |||
f4cef3eaf2 | |||
d23c2eed86 | |||
15695a304e | |||
6319269976 | |||
0ed3d951a7 | |||
2aa39757b4 | |||
39d32a3600 | |||
219d17de34 | |||
9bb5b454e4 | |||
2412f8c531 | |||
8701d684e6 | |||
b543cc34cd | |||
791dbbab9b | |||
ac0b1da3fc | |||
2f97aedc3c | |||
ab544ee965 | |||
fa527f8624 | |||
92ee0aefee | |||
99759ae853 | |||
81930312ff | |||
194fbcdd91 | |||
1e3930aae2 | |||
62dda4d891 | |||
2b870fb9f7 |
6
.idea/compiler.xml
generated
Normal file
6
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<option name="BUILD_PROCESS_HEAP_SIZE" value="1200" />
|
||||
</component>
|
||||
</project>
|
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Kotlin2JvmCompilerArguments">
|
||||
<option name="jvmTarget" value="1.8" />
|
||||
<option name="jvmTarget" value="11" />
|
||||
</component>
|
||||
</project>
|
16
README.md
16
README.md
@ -23,13 +23,15 @@ https://prog8.readthedocs.io/
|
||||
What does Prog8 provide?
|
||||
------------------------
|
||||
|
||||
- big reduction of source code length over raw assembly
|
||||
- reduction of source code length over raw assembly
|
||||
- fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
|
||||
- modularity, symbol scoping, subroutines
|
||||
- various data types other than just bytes (16-bit words, floats, strings)
|
||||
- automatic variable allocations, automatic string and array variables and string sharing
|
||||
- subroutines with an input- and output parameter signature
|
||||
- no stack frame allocations because parameters and local variables are automatically allocated statically
|
||||
- constant folding in expressions and other high-level program optimizations
|
||||
- automatic static variable allocations, automatic string and array variables and string sharing
|
||||
- subroutines with input parameters and result values
|
||||
- high-level program optimizations
|
||||
- small program boilerplate/compilersupport overhead
|
||||
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- conditional branches
|
||||
- floating point operations (requires the C64 Basic ROM routines for this)
|
||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||
@ -37,9 +39,9 @@ What does Prog8 provide?
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
|
||||
- various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
|
||||
- fast execution speed due to compilation to native assembly code
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
- supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, and provides them also on the C64.
|
||||
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
|
||||
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
@ -52,7 +54,7 @@ What does Prog8 provide?
|
||||
|
||||
- "c64": Commodore-64 (6510 CPU = almost a 6502)
|
||||
- "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)!
|
||||
|
||||
|
||||
|
||||
|
@ -1,8 +1,7 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm" version "1.4.30"
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
id "org.jetbrains.kotlin.jvm" version "1.4.32"
|
||||
id 'com.github.johnrengelman.shadow' version '6.1.0'
|
||||
}
|
||||
|
||||
@ -21,7 +20,7 @@ dependencies {
|
||||
implementation project(':compilerAst')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2'
|
||||
// implementation 'net.razorvine:ksim65:1.8'
|
||||
// implementation "com.github.hypfvieh:dbus-java:3.2.4"
|
||||
|
||||
@ -34,6 +33,7 @@ dependencies {
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
useIR = true
|
||||
// verbose = true
|
||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||
}
|
||||
@ -42,6 +42,7 @@ compileKotlin {
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
useIR = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,11 +96,6 @@ test {
|
||||
}
|
||||
|
||||
|
||||
dokka {
|
||||
outputFormat = 'html'
|
||||
outputDirectory = "$buildDir/kdoc"
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '6.7'
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="FacetManager">
|
||||
<facet type="Python" name="Python">
|
||||
<configuration sdkName="Python 3.9" />
|
||||
</facet>
|
||||
</component>
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
@ -14,5 +19,6 @@
|
||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
||||
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
|
||||
</component>
|
||||
</module>
|
@ -428,7 +428,9 @@ var_fac1_greater_f .proc
|
||||
cmp #1
|
||||
beq +
|
||||
lda #0
|
||||
+ rts
|
||||
rts
|
||||
+ lda #1
|
||||
rts
|
||||
.pend
|
||||
|
||||
var_fac1_greatereq_f .proc
|
||||
|
@ -83,7 +83,7 @@ romsub $bc58 = ABS() ; fac1 = ABS(fac1)
|
||||
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
|
||||
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1
|
||||
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
|
@ -2,7 +2,7 @@
|
||||
%import textio
|
||||
|
||||
; bitmap pixel graphics module for the C64
|
||||
; only black/white monchrome 320x200 for now
|
||||
; only black/white monochrome 320x200 for now
|
||||
; assumes bitmap screen memory is $2000-$3fff
|
||||
|
||||
graphics {
|
||||
@ -34,36 +34,33 @@ graphics {
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
; Bresenham algorithm.
|
||||
; 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 {
|
||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||
swap(x1, x2)
|
||||
swap(y1, y2)
|
||||
}
|
||||
word @zp dx = x2-x1 as word
|
||||
word @zp dy = y2-y1
|
||||
word @zp dx = (x2 as word)-x1
|
||||
word @zp dy = (y2 as word)-y1
|
||||
|
||||
if dx==0 {
|
||||
vertical_line(x1, y1, abs(dy)+1 as ubyte)
|
||||
vertical_line(x1, y1, abs(dy) as ubyte +1)
|
||||
return
|
||||
}
|
||||
if dy==0 {
|
||||
if x1>x2
|
||||
x1=x2
|
||||
horizontal_line(x1, y1, abs(dx)+1 as uword)
|
||||
horizontal_line(x1, y1, abs(dx) as uword +1)
|
||||
return
|
||||
}
|
||||
|
||||
; TODO rewrite the rest in optimized assembly
|
||||
|
||||
word @zp d = 0
|
||||
ubyte positive_ix = true
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
}
|
||||
dx *= 2
|
||||
dy *= 2
|
||||
word @zp dx2 = dx*2
|
||||
word @zp dy2 = dy*2
|
||||
internal_plotx = x1
|
||||
|
||||
if dx >= dy {
|
||||
@ -73,10 +70,10 @@ graphics {
|
||||
if internal_plotx==x2
|
||||
return
|
||||
internal_plotx++
|
||||
d += dy
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -85,10 +82,10 @@ graphics {
|
||||
if internal_plotx==x2
|
||||
return
|
||||
internal_plotx--
|
||||
d += dy
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,10 +97,10 @@ graphics {
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
d += dx2
|
||||
if d > dy {
|
||||
internal_plotx++
|
||||
d -= dy
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -112,10 +109,10 @@ graphics {
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
d += dx2
|
||||
if d > dy {
|
||||
internal_plotx--
|
||||
d -= dy
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ c64 {
|
||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||
&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 SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||
|
||||
@ -178,7 +178,7 @@ c64 {
|
||||
|
||||
; ---- 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 $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
@ -202,7 +202,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
@ -211,10 +211,10 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
@ -246,7 +246,7 @@ asmsub STOP2() -> ubyte @A {
|
||||
}
|
||||
|
||||
asmsub RDTIM16() -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits for convenience
|
||||
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr c64.RDTIM
|
||||
@ -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) {
|
||||
%asm {{
|
||||
lda #$80
|
||||
@ -304,27 +310,13 @@ asmsub disable_runstop_and_charsetswitch() clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irqvec_excl() 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) {
|
||||
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 c64.CINV
|
||||
@ -333,9 +325,23 @@ asmsub set_irqvec() clobbers(A) {
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr _irq_handler_init
|
||||
jsr irq.irq
|
||||
_modified jsr $ffff ; modified
|
||||
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
|
||||
; 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 {{
|
||||
sei
|
||||
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 {{
|
||||
sta _modified+1
|
||||
sty _modified+2
|
||||
lda #0
|
||||
adc #0
|
||||
sta set_irq._use_kernal
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
sei
|
||||
jsr _setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
@ -416,12 +429,21 @@ asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
jmp c64.IRQDFRT
|
||||
jsr set_irq._irq_handler_init
|
||||
_modified jsr $ffff ; modified
|
||||
jsr set_irq._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
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
|
||||
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 ----
|
||||
|
||||
}
|
||||
@ -478,7 +478,7 @@ sys {
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to Basic prompt.
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
lda #14
|
||||
@ -489,6 +489,7 @@ sys {
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||
repeat jiffies {
|
||||
ubyte jiff = lsb(c64.RDTIM16())
|
||||
while jiff==lsb(c64.RDTIM16()) {
|
||||
@ -497,6 +498,29 @@ sys {
|
||||
}
|
||||
}
|
||||
|
||||
asmsub waitvsync() clobbers(A) {
|
||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||
; note: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
|
||||
%asm {{
|
||||
- lda c64.RASTER
|
||||
beq -
|
||||
- lda c64.RASTER
|
||||
bne -
|
||||
bit c64.SCROLY
|
||||
bmi -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub waitrastborder() {
|
||||
; --- busy wait till the raster position has reached the bottom screen border (approximately)
|
||||
; note: a more accurate way to do this is by using a raster irq handler instead.
|
||||
%asm {{
|
||||
- bit c64.SCROLY
|
||||
bpl -
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
ldx cx16.r0
|
||||
|
@ -586,7 +586,7 @@ _colormod sta $ffff ; modified
|
||||
}
|
||||
|
||||
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 {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
tax
|
||||
|
@ -7,239 +7,213 @@ conv {
|
||||
|
||||
; ----- number conversions to decimal strings ----
|
||||
|
||||
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
str string_out = "????????????????" ; result buffer for the string conversion routines
|
||||
|
||||
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
ldy #uword2decimal.ASCII_0_OFFSET
|
||||
bne uword2decimal.hex_try200
|
||||
rts
|
||||
phx
|
||||
jsr conv.ubyte2decimal
|
||||
sty string_out
|
||||
sta string_out+1
|
||||
stx string_out+2
|
||||
lda #0
|
||||
sta string_out+3
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- convert 16 bit uword in A/Y to decimal
|
||||
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
|
||||
; (these are terminated by a zero byte so they can be easily printed)
|
||||
; also returns Y = 100's, A = 10's, X = 1's
|
||||
|
||||
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the ubyte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
|
||||
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||
;By Omegamatrix Further optimizations by tepples
|
||||
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
|
||||
|
||||
;HexToDec99
|
||||
; start in A
|
||||
; end with A = 10's, decOnes (also in X)
|
||||
|
||||
;HexToDec255
|
||||
; start in A
|
||||
; end with Y = 100's, A = 10's, decOnes (also in X)
|
||||
|
||||
;HexToDec999
|
||||
; start with A = high byte, Y = low byte
|
||||
; end with Y = 100's, A = 10's, decOnes (also in X)
|
||||
; requires 1 extra temp register on top of decOnes, could combine
|
||||
; these two if HexToDec65535 was eliminated...
|
||||
|
||||
;HexToDec65535
|
||||
; start with A/Y (low/high) as 16 bit value
|
||||
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
|
||||
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
|
||||
|
||||
|
||||
ASCII_0_OFFSET = $30
|
||||
temp = P8ZP_SCRATCH_B1 ; byte in zeropage
|
||||
hexHigh = P8ZP_SCRATCH_W1 ; byte in zeropage
|
||||
hexLow = P8ZP_SCRATCH_W1+1 ; byte in zeropage
|
||||
|
||||
|
||||
HexToDec65535; SUBROUTINE
|
||||
sty hexHigh ;3 @9
|
||||
sta hexLow ;3 @12
|
||||
tya
|
||||
tax ;2 @14
|
||||
lsr a ;2 @16
|
||||
lsr a ;2 @18 integer divide 1024 (result 0-63)
|
||||
|
||||
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
|
||||
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
|
||||
|
||||
;at this point we have a number 1-65 that we have to times by 24,
|
||||
;add to original sum, and Mod 1024 to get a remainder 0-999
|
||||
|
||||
|
||||
sta temp ;3 @25
|
||||
asl a ;2 @27
|
||||
adc temp ;3 @30 x3
|
||||
tay ;2 @32
|
||||
lsr a ;2 @34
|
||||
lsr a ;2 @36
|
||||
lsr a ;2 @38
|
||||
lsr a ;2 @40
|
||||
lsr a ;2 @42
|
||||
tax ;2 @44
|
||||
tya ;2 @46
|
||||
asl a ;2 @48
|
||||
asl a ;2 @50
|
||||
asl a ;2 @52
|
||||
clc ;2 @54
|
||||
adc hexLow ;3 @57
|
||||
sta hexLow ;3 @60
|
||||
txa ;2 @62
|
||||
adc hexHigh ;3 @65
|
||||
sta hexHigh ;3 @68
|
||||
ror a ;2 @70
|
||||
lsr a ;2 @72
|
||||
tay ;2 @74 integer divide 1,000 (result 0-65)
|
||||
|
||||
lsr a ;2 @76 split the 1,000 and 10,000 digit
|
||||
tax ;2 @78
|
||||
lda ShiftedBcdTab,x ;4 @82
|
||||
tax ;2 @84
|
||||
rol a ;2 @86
|
||||
and #$0F ;2 @88
|
||||
ora #ASCII_0_OFFSET
|
||||
sta decThousands ;3 @91
|
||||
txa ;2 @93
|
||||
lsr a ;2 @95
|
||||
lsr a ;2 @97
|
||||
lsr a ;2 @99
|
||||
ora #ASCII_0_OFFSET
|
||||
sta decTenThousands ;3 @102
|
||||
|
||||
lda hexLow ;3 @105
|
||||
cpy temp ;3 @108
|
||||
bmi _doSubtract ;2³ @110/111
|
||||
beq _useZero ;2³ @112/113
|
||||
adc #23 + 24 ;2 @114
|
||||
_doSubtract
|
||||
sbc #23 ;2 @116
|
||||
sta hexLow ;3 @119
|
||||
_useZero
|
||||
lda hexHigh ;3 @122
|
||||
sbc #0 ;2 @124
|
||||
|
||||
Start100s
|
||||
and #$03 ;2 @126
|
||||
tax ;2 @128 0,1,2,3
|
||||
cmp #2 ;2 @130
|
||||
rol a ;2 @132 0,2,5,7
|
||||
ora #ASCII_0_OFFSET
|
||||
tay ;2 @134 Y = Hundreds digit
|
||||
|
||||
lda hexLow ;3 @137
|
||||
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
|
||||
bcs hex_doSub200 ;2³ @143/144
|
||||
|
||||
hex_try200
|
||||
cmp #200 ;2 @145
|
||||
bcc hex_try100 ;2³ @147/148
|
||||
hex_doSub200
|
||||
iny ;2 @149
|
||||
iny ;2 @151
|
||||
sbc #200 ;2 @153
|
||||
hex_try100
|
||||
cmp #100 ;2 @155
|
||||
bcc HexToDec99 ;2³ @157/158
|
||||
iny ;2 @159
|
||||
sbc #100 ;2 @161
|
||||
|
||||
HexToDec99; SUBROUTINE
|
||||
lsr a ;2 @163
|
||||
tax ;2 @165
|
||||
lda ShiftedBcdTab,x ;4 @169
|
||||
tax ;2 @171
|
||||
rol a ;2 @173
|
||||
and #$0F ;2 @175
|
||||
ora #ASCII_0_OFFSET
|
||||
sta decOnes ;3 @178
|
||||
txa ;2 @180
|
||||
lsr a ;2 @182
|
||||
lsr a ;2 @184
|
||||
lsr a ;2 @186
|
||||
ora #ASCII_0_OFFSET
|
||||
|
||||
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
|
||||
sty decHundreds
|
||||
sta decTens
|
||||
ldx decOnes
|
||||
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
|
||||
|
||||
|
||||
HexToDec999; SUBROUTINE
|
||||
sty hexLow ;3 @9
|
||||
jmp Start100s ;3 @12
|
||||
|
||||
Mod100Tab
|
||||
.byte 0,56,12,56+12
|
||||
|
||||
ShiftedBcdTab
|
||||
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
|
||||
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
|
||||
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
|
||||
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
|
||||
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
|
||||
|
||||
decTenThousands .byte 0
|
||||
decThousands .byte 0
|
||||
decHundreds .byte 0
|
||||
decTens .byte 0
|
||||
decOnes .byte 0
|
||||
.byte 0 ; zero-terminate the decimal output string
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
; note: if the number is negative, you have to deal with the '-' yourself!
|
||||
%asm {{
|
||||
cmp #0
|
||||
bpl +
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
+ jmp ubyte2decimal
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
|
||||
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
phx
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
jsr conv.ubyte2decimal
|
||||
_output_byte_digits
|
||||
; hundreds?
|
||||
cpy #'0'
|
||||
beq +
|
||||
pha
|
||||
and #$0f
|
||||
tax
|
||||
ldy _hex_digits,x
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
sta string_out,y
|
||||
pla
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
tax
|
||||
lda _hex_digits,x
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
|
||||
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
|
||||
inc P8ZP_SCRATCH_B1
|
||||
; tens?
|
||||
+ ldy P8ZP_SCRATCH_B1
|
||||
cmp #'0'
|
||||
beq +
|
||||
sta string_out,y
|
||||
iny
|
||||
+ ; ones.
|
||||
txa
|
||||
sta string_out,y
|
||||
iny
|
||||
lda #0
|
||||
sta string_out,y
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
|
||||
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
|
||||
asmsub str_b (byte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the byte in A in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
jsr ubyte2hex
|
||||
sta output
|
||||
sty output+1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
jsr ubyte2hex
|
||||
sta output+2
|
||||
sty output+3
|
||||
rts
|
||||
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
|
||||
phx
|
||||
ldy #0
|
||||
sty P8ZP_SCRATCH_B1
|
||||
cmp #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
sta string_out
|
||||
inc P8ZP_SCRATCH_B1
|
||||
pla
|
||||
+ jsr conv.byte2decimal
|
||||
bra str_ub._output_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_ubhex (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the ubyte in A in hex string form
|
||||
%asm {{
|
||||
jsr conv.ubyte2hex
|
||||
sta string_out
|
||||
sty string_out+1
|
||||
lda #0
|
||||
sta string_out+2
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_ubbin (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- convert the ubyte in A in binary string form
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
ldy #0
|
||||
sty string_out+8
|
||||
ldy #7
|
||||
- lsr P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
bne _digit
|
||||
+ lda #'0'
|
||||
_digit sta string_out,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_uwbin (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- convert the uword in A/Y in binary string form
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
jsr str_ubbin
|
||||
ldy #0
|
||||
sty string_out+16
|
||||
ldy #7
|
||||
- lsr P8ZP_SCRATCH_REG
|
||||
bcc +
|
||||
lda #'1'
|
||||
bne _digit
|
||||
+ lda #'0'
|
||||
_digit sta string_out+8,y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr conv.ubyte2hex
|
||||
sta string_out
|
||||
sty string_out+1
|
||||
pla
|
||||
jsr conv.ubyte2hex
|
||||
sta string_out+2
|
||||
sty string_out+3
|
||||
lda #0
|
||||
sta string_out+4
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
phx
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
sta string_out,y
|
||||
beq +
|
||||
iny
|
||||
bne -
|
||||
+ plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
jsr conv.uword2decimal
|
||||
ldx #0
|
||||
_output_digits
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
iny
|
||||
bne -
|
||||
_gotdigit sta string_out,x
|
||||
inx
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
_end lda #0
|
||||
sta string_out,x
|
||||
plx
|
||||
rts
|
||||
|
||||
_allzero lda #'0'
|
||||
sta string_out,x
|
||||
inx
|
||||
bne _end
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str_w (word value @ AY) clobbers(A,Y) {
|
||||
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl str_uw
|
||||
phx
|
||||
pha
|
||||
lda #'-'
|
||||
sta string_out
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jsr conv.uword2decimal
|
||||
ldx #1
|
||||
bne str_uw._output_digits
|
||||
}}
|
||||
}
|
||||
|
||||
@ -257,7 +231,7 @@ asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1)
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
cmp #'$'
|
||||
beq _hex
|
||||
@ -520,4 +494,243 @@ _stop
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
; ----- low level number conversions to decimal strings ----
|
||||
|
||||
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
%asm {{
|
||||
ldy #uword2decimal.ASCII_0_OFFSET
|
||||
bne uword2decimal.hex_try200
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- convert 16 bit uword in A/Y to decimal
|
||||
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
|
||||
; (these are terminated by a zero byte so they can be easily printed)
|
||||
; also returns Y = 100's, A = 10's, X = 1's
|
||||
|
||||
%asm {{
|
||||
|
||||
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||
;By Omegamatrix Further optimizations by tepples
|
||||
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
|
||||
|
||||
;HexToDec99
|
||||
; start in A
|
||||
; end with A = 10's, decOnes (also in X)
|
||||
|
||||
;HexToDec255
|
||||
; start in A
|
||||
; end with Y = 100's, A = 10's, decOnes (also in X)
|
||||
|
||||
;HexToDec999
|
||||
; start with A = high byte, Y = low byte
|
||||
; end with Y = 100's, A = 10's, decOnes (also in X)
|
||||
; requires 1 extra temp register on top of decOnes, could combine
|
||||
; these two if HexToDec65535 was eliminated...
|
||||
|
||||
;HexToDec65535
|
||||
; start with A/Y (low/high) as 16 bit value
|
||||
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
|
||||
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
|
||||
|
||||
|
||||
ASCII_0_OFFSET = $30
|
||||
temp = P8ZP_SCRATCH_B1 ; byte in zeropage
|
||||
hexHigh = P8ZP_SCRATCH_W1 ; byte in zeropage
|
||||
hexLow = P8ZP_SCRATCH_W1+1 ; byte in zeropage
|
||||
|
||||
|
||||
HexToDec65535; SUBROUTINE
|
||||
sty hexHigh ;3 @9
|
||||
sta hexLow ;3 @12
|
||||
tya
|
||||
tax ;2 @14
|
||||
lsr a ;2 @16
|
||||
lsr a ;2 @18 integer divide 1024 (result 0-63)
|
||||
|
||||
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
|
||||
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
|
||||
|
||||
;at this point we have a number 1-65 that we have to times by 24,
|
||||
;add to original sum, and Mod 1024 to get a remainder 0-999
|
||||
|
||||
|
||||
sta temp ;3 @25
|
||||
asl a ;2 @27
|
||||
adc temp ;3 @30 x3
|
||||
tay ;2 @32
|
||||
lsr a ;2 @34
|
||||
lsr a ;2 @36
|
||||
lsr a ;2 @38
|
||||
lsr a ;2 @40
|
||||
lsr a ;2 @42
|
||||
tax ;2 @44
|
||||
tya ;2 @46
|
||||
asl a ;2 @48
|
||||
asl a ;2 @50
|
||||
asl a ;2 @52
|
||||
clc ;2 @54
|
||||
adc hexLow ;3 @57
|
||||
sta hexLow ;3 @60
|
||||
txa ;2 @62
|
||||
adc hexHigh ;3 @65
|
||||
sta hexHigh ;3 @68
|
||||
ror a ;2 @70
|
||||
lsr a ;2 @72
|
||||
tay ;2 @74 integer divide 1,000 (result 0-65)
|
||||
|
||||
lsr a ;2 @76 split the 1,000 and 10,000 digit
|
||||
tax ;2 @78
|
||||
lda ShiftedBcdTab,x ;4 @82
|
||||
tax ;2 @84
|
||||
rol a ;2 @86
|
||||
and #$0F ;2 @88
|
||||
ora #ASCII_0_OFFSET
|
||||
sta decThousands ;3 @91
|
||||
txa ;2 @93
|
||||
lsr a ;2 @95
|
||||
lsr a ;2 @97
|
||||
lsr a ;2 @99
|
||||
ora #ASCII_0_OFFSET
|
||||
sta decTenThousands ;3 @102
|
||||
|
||||
lda hexLow ;3 @105
|
||||
cpy temp ;3 @108
|
||||
bmi _doSubtract ;2³ @110/111
|
||||
beq _useZero ;2³ @112/113
|
||||
adc #23 + 24 ;2 @114
|
||||
_doSubtract
|
||||
sbc #23 ;2 @116
|
||||
sta hexLow ;3 @119
|
||||
_useZero
|
||||
lda hexHigh ;3 @122
|
||||
sbc #0 ;2 @124
|
||||
|
||||
Start100s
|
||||
and #$03 ;2 @126
|
||||
tax ;2 @128 0,1,2,3
|
||||
cmp #2 ;2 @130
|
||||
rol a ;2 @132 0,2,5,7
|
||||
ora #ASCII_0_OFFSET
|
||||
tay ;2 @134 Y = Hundreds digit
|
||||
|
||||
lda hexLow ;3 @137
|
||||
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
|
||||
bcs hex_doSub200 ;2³ @143/144
|
||||
|
||||
hex_try200
|
||||
cmp #200 ;2 @145
|
||||
bcc hex_try100 ;2³ @147/148
|
||||
hex_doSub200
|
||||
iny ;2 @149
|
||||
iny ;2 @151
|
||||
sbc #200 ;2 @153
|
||||
hex_try100
|
||||
cmp #100 ;2 @155
|
||||
bcc HexToDec99 ;2³ @157/158
|
||||
iny ;2 @159
|
||||
sbc #100 ;2 @161
|
||||
|
||||
HexToDec99; SUBROUTINE
|
||||
lsr a ;2 @163
|
||||
tax ;2 @165
|
||||
lda ShiftedBcdTab,x ;4 @169
|
||||
tax ;2 @171
|
||||
rol a ;2 @173
|
||||
and #$0F ;2 @175
|
||||
ora #ASCII_0_OFFSET
|
||||
sta decOnes ;3 @178
|
||||
txa ;2 @180
|
||||
lsr a ;2 @182
|
||||
lsr a ;2 @184
|
||||
lsr a ;2 @186
|
||||
ora #ASCII_0_OFFSET
|
||||
|
||||
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
|
||||
sty decHundreds
|
||||
sta decTens
|
||||
ldx decOnes
|
||||
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
|
||||
|
||||
|
||||
HexToDec999; SUBROUTINE
|
||||
sty hexLow ;3 @9
|
||||
jmp Start100s ;3 @12
|
||||
|
||||
Mod100Tab
|
||||
.byte 0,56,12,56+12
|
||||
|
||||
ShiftedBcdTab
|
||||
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
|
||||
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
|
||||
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
|
||||
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
|
||||
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
|
||||
|
||||
decTenThousands .byte 0
|
||||
decThousands .byte 0
|
||||
decHundreds .byte 0
|
||||
decTens .byte 0
|
||||
decOnes .byte 0
|
||||
.byte 0 ; zero-terminate the decimal output string
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
; note: if the number is negative, you have to deal with the '-' yourself!
|
||||
%asm {{
|
||||
cmp #0
|
||||
bpl +
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
+ jmp ubyte2decimal
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
|
||||
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG
|
||||
pha
|
||||
and #$0f
|
||||
tax
|
||||
ldy _hex_digits,x
|
||||
pla
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
tax
|
||||
lda _hex_digits,x
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
|
||||
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
|
||||
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
jsr ubyte2hex
|
||||
sta output
|
||||
sty output+1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
jsr ubyte2hex
|
||||
sta output+2
|
||||
sty output+3
|
||||
rts
|
||||
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,9 @@
|
||||
%option enable_floats
|
||||
|
||||
floats {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
; ---- this block contains C-64 compatible floating point related functions ----
|
||||
; the addresses are from cx16 V39 emulator and roms! they won't work on older versions.
|
||||
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
@ -43,46 +45,44 @@ romsub $fe1e = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
||||
romsub $fe24 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
romsub $fe2a = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
romsub $fe33 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
romsub $fe36 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $fe3c = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||
romsub $fe3f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||
romsub $fe42 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
||||
romsub $fe30 = CONUPK(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
romsub $fe33 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $fe36 = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||
romsub $fe39 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||
romsub $fe3c = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
||||
|
||||
romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||
romsub $fe54 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $fe5a = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $fe5d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||
romsub $fe6c = ABS() ; fac1 = ABS(fac1)
|
||||
romsub $fe6f = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
romsub $fe78 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||
romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
; note: there is no FPWR() on the Cx16
|
||||
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
|
||||
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
|
||||
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
romsub $feab = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $feae = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
romsub $fe42 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $fe45 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
romsub $fe48 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $fe4b = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||
romsub $fe4e = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $fe54 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $fe57 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $fe5a = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||
romsub $fe66 = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
|
||||
romsub $fe69 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
romsub $fe72 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||
romsub $fe78 = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
romsub $fe7b = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
romsub $fe81 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $fe84 = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
romsub $fe8a = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
|
||||
romsub $fe8d = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $fe96 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $fe99 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $fe9c = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
romsub $fe9f = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $fea2 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
|
||||
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
%asm {{
|
||||
phx
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sta _tmp
|
||||
sty P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_W2
|
||||
ldy _tmp
|
||||
jsr GIVAYF ; load it as signed... correct afterwards
|
||||
lda P8ZP_SCRATCH_B1
|
||||
bpl +
|
||||
@ -91,6 +91,7 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
jsr FADD
|
||||
+ plx
|
||||
rts
|
||||
_tmp .byte 0
|
||||
_flt65536 .byte 145,0,0,0,0 ; 65536.0
|
||||
}}
|
||||
}
|
||||
@ -128,6 +129,14 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FREADUY (ubyte value @Y) {
|
||||
; -- 8 bit unsigned Y -> float in fac1
|
||||
%asm {{
|
||||
lda #0
|
||||
jmp GIVAYF
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_f (float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
; Bitmap pixel graphics routines for the CommanderX16
|
||||
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
|
||||
; (These modes are not supported by the documented GRAPH_xxxx kernel routines)
|
||||
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
|
||||
;
|
||||
; 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.
|
||||
@ -15,13 +15,12 @@
|
||||
; 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 (unsupported TODO not yet implemented)
|
||||
; mode 3 = bitmap 320 x 240 x 16c (unsupported TODO not yet implemented)
|
||||
; 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 (unsupported TODO being implemented)
|
||||
; mode 7 = bitmap 640 x 480 x 16c (unsupported due to lack of VRAM)
|
||||
; mode 8 = bitmap 640 x 480 x 256c (unsupported due to lack of VRAM)
|
||||
; 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?
|
||||
|
||||
@ -37,7 +36,7 @@ gfx2 {
|
||||
sub screen_mode(ubyte mode) {
|
||||
when mode {
|
||||
1 -> {
|
||||
; lores monchrome
|
||||
; lores monochrome
|
||||
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
|
||||
cx16.VERA_DC_HSCALE = 64
|
||||
cx16.VERA_DC_VSCALE = 64
|
||||
@ -85,7 +84,6 @@ gfx2 {
|
||||
height = 480
|
||||
bpp = 2
|
||||
}
|
||||
; modes 7 and 8 not supported due to lack of VRAM
|
||||
else -> {
|
||||
; back to default text mode and colors
|
||||
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
|
||||
@ -168,7 +166,7 @@ gfx2 {
|
||||
if separate_pixels as uword > length
|
||||
separate_pixels = lsb(length)
|
||||
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)
|
||||
x++
|
||||
}
|
||||
@ -210,7 +208,7 @@ _loop lda length
|
||||
_done
|
||||
}}
|
||||
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)
|
||||
x++
|
||||
}
|
||||
@ -299,6 +297,7 @@ _done
|
||||
1, 5 -> {
|
||||
; monochrome, either resolution
|
||||
; 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.
|
||||
; 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
|
||||
if active_mode>=5
|
||||
@ -326,7 +325,7 @@ _done
|
||||
}
|
||||
} else {
|
||||
; stippling.
|
||||
height = (height+1)/2
|
||||
height = (height+1)/2 ; TODO is the line sometimes 1 pixel too long now because of rounding?
|
||||
%asm {{
|
||||
lda x
|
||||
eor y
|
||||
@ -375,7 +374,8 @@ _done
|
||||
lda cx16.VERA_ADDR_M
|
||||
adc #0
|
||||
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
|
||||
; sta cx16.VERA_ADDR_H
|
||||
}}
|
||||
@ -399,11 +399,12 @@ _done
|
||||
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
|
||||
; TODO optimize this vertical line loop in pure assembly
|
||||
color &= 3
|
||||
color <<= gfx2.plot.shift4c[lsb(x) & 3]
|
||||
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
|
||||
@ -432,48 +433,46 @@ _done
|
||||
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
|
||||
; Bresenham algorithm.
|
||||
; 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 {
|
||||
; make sure dy is always positive to have only 4 instead of 8 special cases
|
||||
swap(x1, x2)
|
||||
swap(y1, y2)
|
||||
}
|
||||
word @zp dx = x2-x1 as word
|
||||
word @zp dy = y2-y1 as word
|
||||
word @zp dx = (x2 as word)-x1
|
||||
word @zp dy = (y2 as word)-y1
|
||||
|
||||
if dx==0 {
|
||||
vertical_line(x1, y1, abs(dy)+1 as uword, color)
|
||||
vertical_line(x1, y1, abs(dy) as uword +1, color)
|
||||
return
|
||||
}
|
||||
if dy==0 {
|
||||
if x1>x2
|
||||
x1=x2
|
||||
horizontal_line(x1, y1, abs(dx)+1 as uword, color)
|
||||
horizontal_line(x1, y1, abs(dx) as uword +1, color)
|
||||
return
|
||||
}
|
||||
|
||||
; TODO rewrite the rest in optimized assembly (or reuse GRAPH_draw_line if we can get the FB replacement vector layer working)
|
||||
word @zp d = 0
|
||||
ubyte positive_ix = true
|
||||
cx16.r13 = true ; 'positive_ix'
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
cx16.r13 = false
|
||||
}
|
||||
dx *= 2
|
||||
dy *= 2
|
||||
word @zp dx2 = dx*2
|
||||
word @zp dy2 = dy*2
|
||||
cx16.r14 = x1 ; internal plot X
|
||||
|
||||
if dx >= dy {
|
||||
if positive_ix {
|
||||
if cx16.r13 {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, color)
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14++
|
||||
d += dy
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -482,25 +481,25 @@ _done
|
||||
if cx16.r14==x2
|
||||
return
|
||||
cx16.r14--
|
||||
d += dy
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if positive_ix {
|
||||
if cx16.r13 {
|
||||
repeat {
|
||||
plot(cx16.r14, y1, color)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14++
|
||||
d -= dy
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -509,10 +508,10 @@ _done
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
d += dx2
|
||||
if d > dy {
|
||||
cx16.r14--
|
||||
d -= dy
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -812,6 +811,7 @@ _done
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
cx16.vaddr(charset_bank, chardataptr, 1, 1)
|
||||
repeat 8 {
|
||||
; TODO rewrite this inner loop fully in assembly
|
||||
position(x,y)
|
||||
y++
|
||||
%asm {{
|
||||
@ -840,7 +840,7 @@ _done
|
||||
while @(sctextptr) {
|
||||
chardataptr = charset_addr + (@(sctextptr) as uword)*8
|
||||
repeat 8 {
|
||||
; TODO rewrite this inner loop in assembly
|
||||
; TODO rewrite this inner loop fully in assembly
|
||||
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
|
||||
repeat 8 {
|
||||
charbits <<= 1
|
||||
@ -877,15 +877,31 @@ _done
|
||||
}}
|
||||
}
|
||||
|
||||
sub addr_mul_24_for_highres_4c(uword yy, uword xx) {
|
||||
; TODO turn into asmsub
|
||||
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)
|
||||
cx16.r0 = yy*128
|
||||
cx16.r2 = yy*32
|
||||
xx >>= 2
|
||||
|
||||
%asm {{
|
||||
; add r2 and xx to r0 (24-bits)
|
||||
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
|
||||
@ -898,60 +914,61 @@ _done
|
||||
inc cx16.r1
|
||||
+ clc
|
||||
lda cx16.r0
|
||||
adc xx
|
||||
adc cx16.r3
|
||||
sta cx16.r0
|
||||
lda cx16.r0+1
|
||||
adc xx+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 {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r0+1
|
||||
sta cx16.r1
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda cx16.r0
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
sta cx16.r0
|
||||
lda P8ZP_SCRATCH_B1
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
sta cx16.r0+1
|
||||
bcc +
|
||||
inc cx16.r1
|
||||
+ ; now add the value to this 24-bits number
|
||||
lda cx16.r0
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W1
|
||||
sta cx16.r0
|
||||
lda cx16.r0+1
|
||||
adc P8ZP_SCRATCH_W1+1
|
||||
sta cx16.r0+1
|
||||
bcc +
|
||||
inc cx16.r1
|
||||
+ lda cx16.r1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
; yy * 320 + xx (24 bits calculation)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r0
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r0+1
|
||||
sta cx16.r1
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda cx16.r0
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
sta cx16.r0
|
||||
lda P8ZP_SCRATCH_B1
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
sta cx16.r0+1
|
||||
bcc +
|
||||
inc cx16.r1
|
||||
+ ; now add the value to this 24-bits number
|
||||
lda cx16.r0
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W1
|
||||
sta cx16.r0
|
||||
lda cx16.r0+1
|
||||
adc P8ZP_SCRATCH_W1+1
|
||||
sta cx16.r0+1
|
||||
bcc +
|
||||
inc cx16.r1
|
||||
+ lda cx16.r1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
; Bitmap pixel graphics module for the CommanderX16
|
||||
; wraps the graphics functions that are in ROM.
|
||||
; only black/white monchrome 320x200 for now. (i.e. truncated at the bottom)
|
||||
; only black/white monochrome 320x200 for now. (i.e. truncated at the bottom)
|
||||
; For full-screen 640x480 or 320x240 graphics, use the "gfx2" module instead. (but that is Cx16-specific)
|
||||
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
|
||||
|
||||
|
@ -9,8 +9,10 @@ palette {
|
||||
ubyte c
|
||||
|
||||
sub set_color(ubyte index, uword color) {
|
||||
cx16.vpoke(1, $fa00+index*2, lsb(color))
|
||||
cx16.vpoke(1, $fa01+index*2, msb(color))
|
||||
vera_palette_ptr = $fa00+index*2
|
||||
cx16.vpoke(1, vera_palette_ptr, lsb(color))
|
||||
vera_palette_ptr++
|
||||
cx16.vpoke(1, vera_palette_ptr, msb(color))
|
||||
}
|
||||
|
||||
sub set_rgb4(uword palette_bytes_ptr, uword num_colors) {
|
||||
@ -98,7 +100,7 @@ palette {
|
||||
$666, ; 12 = medium grey
|
||||
$9D8, ; 13 = light green
|
||||
$65B, ; 14 = light blue
|
||||
$999 ; 15 = light grey
|
||||
$999 ; 15 = light grey
|
||||
]
|
||||
|
||||
uword[] C64_colorpalette_pepto = [ ; # this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||
@ -117,7 +119,7 @@ palette {
|
||||
$777, ; 12 = medium grey
|
||||
$af9, ; 13 = light green
|
||||
$76e, ; 14 = light blue
|
||||
$bbb ; 15 = light grey
|
||||
$bbb ; 15 = light grey
|
||||
]
|
||||
|
||||
uword[] C64_colorpalette_light = [ ; this is a lighter palette
|
||||
|
@ -24,7 +24,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; re
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See MEMTOP2
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See cx16.numbanks()
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
@ -35,7 +35,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
@ -44,10 +44,10 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
|
||||
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
@ -74,7 +74,7 @@ asmsub STOP2() -> ubyte @A {
|
||||
}
|
||||
|
||||
asmsub RDTIM16() -> uword @AY {
|
||||
; -- like RDTIM() but only returning the lower 16 bits for convenience
|
||||
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.RDTIM
|
||||
@ -87,25 +87,15 @@ asmsub RDTIM16() -> uword @AY {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub MEMTOP2() -> ubyte @A {
|
||||
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks.
|
||||
%asm {{
|
||||
phx
|
||||
sec
|
||||
jsr c64.MEMTOP
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
|
||||
; 65c02 hardware vectors:
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
; irq and hardware vectors:
|
||||
&uword CINV = $0314 ; IRQ vector (in ram)
|
||||
&uword NMI_VEC = $FFFA ; 65c02 nmi 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
|
||||
@ -171,7 +161,7 @@ cx16 {
|
||||
|
||||
; I/O
|
||||
|
||||
const uword via1 = $9f60 ;VIA 6522 #1
|
||||
const uword via1 = $9f00 ;VIA 6522 #1
|
||||
&ubyte d1prb = via1+0
|
||||
&ubyte d1pra = via1+1
|
||||
&ubyte d1ddrb = via1+2
|
||||
@ -189,7 +179,7 @@ cx16 {
|
||||
&ubyte d1ier = via1+14
|
||||
&ubyte d1ora = via1+15
|
||||
|
||||
const uword via2 = $9f70 ;VIA 6522 #2
|
||||
const uword via2 = $9f10 ;VIA 6522 #2
|
||||
&ubyte d2prb = via2+0
|
||||
&ubyte d2pra = via2+1
|
||||
&ubyte d2ddrb = via2+2
|
||||
@ -207,6 +197,11 @@ cx16 {
|
||||
&ubyte d2ier = via2+14
|
||||
&ubyte d2ora = via2+15
|
||||
|
||||
&ubyte ym2151adr = $9f40
|
||||
&ubyte ym2151dat = $9f41
|
||||
|
||||
const uword extdev = $9f60
|
||||
|
||||
|
||||
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||
; spelling of the names is taken from the Commander X-16 rom sources
|
||||
@ -293,16 +288,25 @@ romsub $fecc = monitor() clobbers(A,X,Y)
|
||||
inline asmsub rombank(ubyte rombank @A) {
|
||||
; -- set the rom banks
|
||||
%asm {{
|
||||
sta $01 ; rom bank register (new)
|
||||
sta cx16.d1prb ; rom bank register (old)
|
||||
sta $01 ; rom bank register (v39+, used to be cx16.d1prb $9f60 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub rambank(ubyte rambank @A) {
|
||||
; -- set the ram bank
|
||||
%asm {{
|
||||
sta $00 ; ram bank register (new)
|
||||
sta cx16.d1pra ; ram bank register (old)
|
||||
sta $00 ; ram bank register (v39+, used to be cx16.d1pra $9f61 in v38)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub numbanks() -> ubyte @A {
|
||||
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
|
||||
%asm {{
|
||||
phx
|
||||
sec
|
||||
jsr c64.MEMTOP
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
@ -349,75 +353,126 @@ asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrO
|
||||
}
|
||||
|
||||
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
||||
; -- write a single byte to VERA's video memory
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
sty cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
; -- write a single byte to VERA's video memory
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
sty cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vpoke_or(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
|
||||
; -- or a single byte to the value already in the VERA's video memory at that location
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
tya
|
||||
ora cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
; -- or a single byte to the value already in the VERA's video memory at that location
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
tya
|
||||
ora cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vpoke_and(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
|
||||
; -- and a single byte to the value already in the VERA's video memory at that location
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
tya
|
||||
and cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
; -- and a single byte to the value already in the VERA's video memory at that location
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
tya
|
||||
and cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
|
||||
; -- xor a single byte to the value already in the VERA's video memory at that location
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
tya
|
||||
eor cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
; -- xor a single byte to the value already in the VERA's video memory at that location
|
||||
; note: inefficient when writing multiple sequential bytes!
|
||||
%asm {{
|
||||
stz cx16.VERA_CTRL
|
||||
and #1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda cx16.r0
|
||||
sta cx16.VERA_ADDR_L
|
||||
lda cx16.r0+1
|
||||
sta cx16.VERA_ADDR_M
|
||||
tya
|
||||
eor cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
|
||||
; -- like the basic command VLOAD "filename",device,bank,address
|
||||
; loads a file into video memory in the given bank:address, returns success in A
|
||||
; !! NOTE !! the V38 ROMs contain a bug in the LOAD code that makes the load address not work correctly,
|
||||
; it works fine when loading from local filesystem
|
||||
%asm {{
|
||||
; -- load a file into video ram
|
||||
phx
|
||||
pha
|
||||
tya
|
||||
tax
|
||||
lda #1
|
||||
ldy #0
|
||||
jsr c64.SETLFS
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr prog8_lib.strlen
|
||||
tya
|
||||
ldx cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jsr c64.SETNAM
|
||||
pla
|
||||
clc
|
||||
adc #2
|
||||
ldx cx16.r1
|
||||
ldy cx16.r1+1
|
||||
stz P8ZP_SCRATCH_B1
|
||||
jsr c64.LOAD
|
||||
bcs +
|
||||
inc P8ZP_SCRATCH_B1
|
||||
+ jsr c64.CLRCHN
|
||||
lda #1
|
||||
jsr c64.CLOSE
|
||||
plx
|
||||
lda P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX {
|
||||
; convenience routine to get the joystick state without requiring inline assembly that deals with the multiple return values.
|
||||
; Also disables interrupts to avoid the IRQ race condition mentioned here: https://github.com/commanderx16/x16-rom/issues/203
|
||||
; TODO once that issue is resolved, this routine can be redefined as: romsub $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX
|
||||
%asm {{
|
||||
sei
|
||||
jsr cx16.joystick_get
|
||||
cli
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
sub FB_set_pixels_from_buf(uword buffer, uword count) {
|
||||
%asm {{
|
||||
; -- This is replacement code for the normal FB_set_pixels subroutine in ROM
|
||||
@ -453,17 +508,15 @@ _loop ldy #0
|
||||
}
|
||||
|
||||
; ---- system stuff -----
|
||||
asmsub init_system() {
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
;stz $00
|
||||
;stz $01
|
||||
;stz d1prb ; select rom bank 0
|
||||
lda #$80
|
||||
sta VERA_CTRL
|
||||
stz $01 ; select rom bank 0 (enable kernal)
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
@ -485,8 +538,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 {
|
||||
; ------- lowlevel system routines --------
|
||||
|
||||
@ -494,11 +716,10 @@ sys {
|
||||
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to Basic prompt.
|
||||
; Soft-reset the system back to initial power-on Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
stz $01 ; bank the kernal in (new rom bank register)
|
||||
stz cx16.d1prb ; bank the kernal in (old rom bank register)
|
||||
stz $01 ; bank the kernal in
|
||||
jmp (cx16.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
@ -513,6 +734,21 @@ sys {
|
||||
}
|
||||
}
|
||||
|
||||
asmsub waitvsync() clobbers(A, X, Y) {
|
||||
; --- busy wait till the next vsync has occurred (approximately), without depending on custom irq handling.
|
||||
; note: system vsync irq handler has to be active for this routine to work.
|
||||
; note 2: a more accurate way to wait for vsync is to set up a vsync irq handler instead.
|
||||
%asm {{
|
||||
jsr c64.RDTIM
|
||||
sta _mod + 1
|
||||
inc _mod + 1
|
||||
_loop jsr c64.RDTIM
|
||||
_mod cmp #255 ; modified
|
||||
bne _loop
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
|
||||
%asm {{
|
||||
sta cx16.r2
|
||||
|
@ -420,7 +420,7 @@ _print_byte_digits
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
jmp _ones
|
||||
bra _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
@ -443,7 +443,7 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
jsr c64.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
bra print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
@ -494,7 +494,7 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
jsr print_ubbin
|
||||
pla
|
||||
clc
|
||||
jmp print_ubbin
|
||||
bra print_ubbin
|
||||
}}
|
||||
}
|
||||
|
||||
@ -507,7 +507,7 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
jsr print_ubhex
|
||||
pla
|
||||
clc
|
||||
jmp print_ubhex
|
||||
bra print_ubhex
|
||||
}}
|
||||
}
|
||||
|
||||
@ -570,7 +570,7 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp print_uw
|
||||
+ bra print_uw
|
||||
}}
|
||||
}
|
||||
|
||||
@ -626,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) {
|
||||
; ---- 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 {{
|
||||
pha
|
||||
txa
|
||||
@ -657,6 +659,8 @@ asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||
|
||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
; ---- 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 {{
|
||||
phx
|
||||
lda column
|
||||
@ -685,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) {
|
||||
; ---- safe wrapper around PLOT kernel routine, to save the X register.
|
||||
; ---- safe wrapper around PLOT kernal routine, to save the X register.
|
||||
%asm {{
|
||||
phx
|
||||
tax
|
||||
|
@ -365,6 +365,7 @@ io_error:
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
c64.SETLFS(1, drivenumber, 0)
|
||||
uword end_address = address + size
|
||||
first_byte = 0 ; result var reuse
|
||||
|
||||
%asm {{
|
||||
lda address
|
||||
@ -381,7 +382,6 @@ io_error:
|
||||
plp
|
||||
}}
|
||||
|
||||
first_byte = 0 ; result var reuse
|
||||
if_cc
|
||||
first_byte = c64.READST()==0
|
||||
|
||||
|
@ -244,8 +244,8 @@ randseed .proc
|
||||
.pend
|
||||
|
||||
|
||||
randbyte .proc
|
||||
; -- 8-bit pseudo random number generator into A
|
||||
fast_randbyte .proc
|
||||
; -- fast but bad 8-bit pseudo random number generator into A
|
||||
lda _seed
|
||||
beq _eor
|
||||
asl a
|
||||
@ -263,6 +263,10 @@ _seed .byte $3a
|
||||
|
||||
.pend
|
||||
|
||||
randbyte .proc
|
||||
; -- 8 bit pseudo random number generator into A (by just reusing randword)
|
||||
jmp randword
|
||||
.pend
|
||||
|
||||
randword .proc
|
||||
; -- 16 bit pseudo random number generator into AY
|
||||
@ -1537,3 +1541,71 @@ _negative lsr a
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
square .proc
|
||||
; -- calculate square root of signed word in AY, result in AY
|
||||
; routine by Lee Davsion, source: http://6502.org/source/integers/square.htm
|
||||
; using this routine is about twice as fast as doing a regular multiplication.
|
||||
;
|
||||
; Calculates the 16 bit unsigned integer square of the signed 16 bit integer in
|
||||
; Numberl/Numberh. The result is always in the range 0 to 65025 and is held in
|
||||
; Squarel/Squareh
|
||||
;
|
||||
; The maximum input range is only +/-255 and no checking is done to ensure that
|
||||
; this is so.
|
||||
;
|
||||
; This routine is useful if you are trying to draw circles as for any circle
|
||||
;
|
||||
; x^2+y^2=r^2 where x and y are the co-ordinates of any point on the circle and
|
||||
; r is the circle radius
|
||||
|
||||
numberl = P8ZP_SCRATCH_W1 ; number to square low byte
|
||||
numberh = P8ZP_SCRATCH_W1+1 ; number to square high byte
|
||||
squarel = P8ZP_SCRATCH_W2 ; square low byte
|
||||
squareh = P8ZP_SCRATCH_W2+1 ; square high byte
|
||||
tempsq = P8ZP_SCRATCH_B1 ; temp byte for intermediate result
|
||||
|
||||
sta numberl
|
||||
sty numberh
|
||||
stx P8ZP_SCRATCH_REG
|
||||
|
||||
lda #$00 ; clear a
|
||||
sta squarel ; clear square low byte
|
||||
; (no need to clear the high byte, it gets shifted out)
|
||||
lda numberl ; get number low byte
|
||||
ldx numberh ; get number high byte
|
||||
bpl _nonneg ; if +ve don't negate it
|
||||
; else do a two's complement
|
||||
eor #$ff ; invert
|
||||
sec ; +1
|
||||
adc #$00 ; and add it
|
||||
|
||||
_nonneg:
|
||||
sta tempsq ; save abs(number)
|
||||
ldx #$08 ; set bit count
|
||||
|
||||
_nextr2bit:
|
||||
asl squarel ; low byte *2
|
||||
rol squareh ; high byte *2+carry from low
|
||||
asl a ; shift number byte
|
||||
bcc _nosqadd ; don't do add if c = 0
|
||||
tay ; save a
|
||||
clc ; clear carry for add
|
||||
lda tempsq ; get number
|
||||
adc squarel ; add number^2 low byte
|
||||
sta squarel ; save number^2 low byte
|
||||
lda #$00 ; clear a
|
||||
adc squareh ; add number^2 high byte
|
||||
sta squareh ; save number^2 high byte
|
||||
tya ; get a back
|
||||
|
||||
_nosqadd:
|
||||
dex ; decrement bit count
|
||||
bne _nextr2bit ; go do next bit
|
||||
|
||||
lda squarel
|
||||
ldy squareh
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
|
||||
.pend
|
||||
|
@ -387,6 +387,14 @@ func_sqrt16_into_A .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_fastrnd8_stack .proc
|
||||
; -- put a random ubyte on the estack (using fast but bad RNG)
|
||||
jsr math.fast_randbyte
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rnd_stack .proc
|
||||
; -- put a random ubyte on the estack
|
||||
jsr math.randbyte
|
||||
@ -432,6 +440,7 @@ func_min_ub_stack .proc
|
||||
func_min_b_into_A .proc
|
||||
; -- min(barray) -> A. (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
tay
|
||||
dey
|
||||
lda #127
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
@ -548,6 +557,7 @@ func_min_w_stack .proc
|
||||
func_max_ub_into_A .proc
|
||||
; -- max(ubarray) -> A (array in P8ZP_SCRATCH_W1, num elements in A)
|
||||
tay
|
||||
dey
|
||||
lda #0
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
|
@ -1072,3 +1072,14 @@ sign_extend_AY_byte .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
strlen .proc
|
||||
; -- returns the number of bytes in the string in AY, in Y.
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
beq +
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
.pend
|
||||
|
@ -1 +1 @@
|
||||
6.1
|
||||
6.5-BETA
|
||||
|
@ -8,7 +8,6 @@ import prog8.ast.base.AstException
|
||||
import prog8.compiler.CompilationResult
|
||||
import prog8.compiler.compileProgram
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.parser.ParsingFailedError
|
||||
import java.nio.file.FileSystems
|
||||
@ -117,7 +116,7 @@ private fun compileMain(args: Array<String>) {
|
||||
if (compilationResult.programName.isEmpty())
|
||||
println("\nCan't start emulator because no program was assembled.")
|
||||
else {
|
||||
ICompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
|
||||
compilationResult.compTarget.machine.launchEmulator(compilationResult.programName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@ -8,25 +9,26 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.add(decl.name to decl)
|
||||
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// a numeric vardecl without an initial value is initialized with zero,
|
||||
// unless there's already an assignment below, that initializes the value
|
||||
// A numeric vardecl without an initial value is initialized with zero,
|
||||
// unless there's already an assignment below, that initializes the value.
|
||||
// This allows you to restart the program and have the same starting values of the variables
|
||||
if(decl.allowInitializeWithZero)
|
||||
{
|
||||
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
|
||||
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
|
||||
if (nextAssign != null && nextAssign.target isSameAs IdentifierReference(listOf(decl.name), Position.DUMMY))
|
||||
decl.value = null
|
||||
else
|
||||
else {
|
||||
decl.value = decl.zeroElementValue()
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -67,9 +69,11 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
}
|
||||
|
||||
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
|
||||
private val addedIfConditionVars = mutableSetOf<Pair<Subroutine, String>>()
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.clear()
|
||||
addedIfConditionVars.clear()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -80,17 +84,22 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
val sub = scope.definingSubroutine()
|
||||
if (sub != null) {
|
||||
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
|
||||
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
|
||||
val replaceVardecls =numericVarsWithValue.map {
|
||||
val initValue = it.value!! // assume here that value has always been set by now
|
||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||
val assign = Assignment(target, initValue, it.position)
|
||||
initValue.parent = assign
|
||||
IAstModification.ReplaceNode(it, assign, scope)
|
||||
val replacements = mutableListOf<IAstModification>()
|
||||
val movements = mutableListOf<IAstModification.InsertFirst>()
|
||||
|
||||
for(decl in decls) {
|
||||
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, decl.value!!, decl.position)
|
||||
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
} else {
|
||||
replacements.add(IAstModification.Remove(decl, scope))
|
||||
}
|
||||
movements.add(IAstModification.InsertFirst(decl, sub))
|
||||
}
|
||||
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
|
||||
return replaceVardecls + moveVardeclsUp
|
||||
return replacements + movements
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
@ -107,7 +116,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.
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
@ -154,16 +163,6 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
// The only place for now where we can do this is for:
|
||||
// asmsub register pair parameter.
|
||||
|
||||
if(typecast.type in WordDatatypes) {
|
||||
val fcall = typecast.parent as? IFunctionCall
|
||||
if (fcall != null) {
|
||||
val sub = fcall.target.targetStatement(program) as? Subroutine
|
||||
if (sub != null && sub.isAsmSubroutine) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type==DataType.UWORD) {
|
||||
if(typecast.expression is IdentifierReference) {
|
||||
@ -194,9 +193,53 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||
}
|
||||
|
||||
if((binExpr.left as? NumericLiteralValue)?.number==0)
|
||||
throw CompilerException("if 0==X should have been swapped to if X==0")
|
||||
|
||||
// split the conditional expression into separate variables if the operand(s) is not simple.
|
||||
// DISABLED FOR NOW AS IT GENEREATES LARGER CODE IN THE SIMPLE CASES LIKE IF X {...} or IF NOT X {...}
|
||||
// val modifications = mutableListOf<IAstModification>()
|
||||
// if(!binExpr.left.isSimple) {
|
||||
// val sub = binExpr.definingSubroutine()!!
|
||||
// val (variable, isNew, assignment) = addIfOperandVar(sub, "left", binExpr.left)
|
||||
// if(isNew)
|
||||
// modifications.add(IAstModification.InsertFirst(variable, sub))
|
||||
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
|
||||
// modifications.add(IAstModification.ReplaceNode(binExpr.left, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
|
||||
// addedIfConditionVars.add(Pair(sub, variable.name))
|
||||
// }
|
||||
// if(!binExpr.right.isSimple) {
|
||||
// val sub = binExpr.definingSubroutine()!!
|
||||
// val (variable, isNew, assignment) = addIfOperandVar(sub, "right", binExpr.right)
|
||||
// if(isNew)
|
||||
// modifications.add(IAstModification.InsertFirst(variable, sub))
|
||||
// modifications.add(IAstModification.InsertBefore(ifStatement, assignment, parent as INameScope))
|
||||
// modifications.add(IAstModification.ReplaceNode(binExpr.right, IdentifierReference(listOf(variable.name), binExpr.position), binExpr))
|
||||
// addedIfConditionVars.add(Pair(sub, variable.name))
|
||||
// }
|
||||
// return modifications
|
||||
return noModifications
|
||||
}
|
||||
|
||||
// private fun addIfOperandVar(sub: Subroutine, side: String, operand: Expression): Triple<VarDecl, Boolean, Assignment> {
|
||||
// val dt = operand.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
// val varname = "prog8_ifvar_${side}_${dt.name.toLowerCase()}"
|
||||
// val tgt = AssignTarget(IdentifierReference(listOf(varname), operand.position), null, null, operand.position)
|
||||
// val assign = Assignment(tgt, operand, operand.position)
|
||||
// if(Pair(sub, varname) in addedIfConditionVars) {
|
||||
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
|
||||
// return Triple(vardecl, false, assign)
|
||||
// }
|
||||
// val existing = sub.statements.firstOrNull { it is VarDecl && it.name == varname} as VarDecl?
|
||||
// return if (existing == null) {
|
||||
// val vardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, varname, null, null, false, true, operand.position)
|
||||
// Triple(vardecl, true, assign)
|
||||
// } else {
|
||||
// Triple(existing, false, assign)
|
||||
// }
|
||||
// }
|
||||
|
||||
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val binExpr = untilLoop.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
@ -216,4 +259,93 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource==listOf("cmp")) {
|
||||
// if the datatype of the arguments of cmp() are different, cast the byte one to word.
|
||||
val arg1 = functionCallStatement.args[0]
|
||||
val arg2 = functionCallStatement.args[1]
|
||||
val dt1 = arg1.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val dt2 = arg2.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes)
|
||||
return noModifications
|
||||
val cast1 = TypecastExpression(arg1, if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(arg1, cast1, functionCallStatement))
|
||||
} else {
|
||||
if(dt2 in WordDatatypes)
|
||||
return noModifications
|
||||
val cast2 = TypecastExpression(arg2, if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
|
||||
return listOf(IAstModification.ReplaceNode(arg2, cast2, functionCallStatement))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val containingStatement = getContainingStatement(arrayIndexedExpression)
|
||||
if(getComplexArrayIndexedExpressions(containingStatement).size > 1) {
|
||||
errors.err("it's not possible to use more than one complex array indexing expression in a single statement; break it up via a temporary variable for instance", containingStatement.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
val index = arrayIndexedExpression.indexer.indexExpr
|
||||
if(index !is NumericLiteralValue && index !is IdentifierReference) {
|
||||
// replace complex indexing expression with a temp variable to hold the computed index first
|
||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun getComplexArrayIndexedExpressions(stmt: Statement): List<ArrayIndexedExpression> {
|
||||
|
||||
class Searcher : IAstVisitor {
|
||||
val complexArrayIndexedExpressions = mutableListOf<ArrayIndexedExpression>()
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
val ix = arrayIndexedExpression.indexer.indexExpr
|
||||
if(ix !is NumericLiteralValue && ix !is IdentifierReference)
|
||||
complexArrayIndexedExpressions.add(arrayIndexedExpression)
|
||||
}
|
||||
|
||||
override fun visit(branchStatement: BranchStatement) {}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {}
|
||||
|
||||
override fun visit(ifStatement: IfStatement) {
|
||||
ifStatement.condition.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
untilLoop.condition.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
val searcher = Searcher()
|
||||
stmt.accept(searcher)
|
||||
return searcher.complexArrayIndexedExpressions
|
||||
}
|
||||
|
||||
private fun getContainingStatement(expression: Expression): Statement {
|
||||
var node: Node = expression
|
||||
while(node !is Statement)
|
||||
node = node.parent
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val statement = expr.containingStatement()
|
||||
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
|
||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||
val target = AssignTarget(IdentifierReference(listOf("cx16", "r9"), expr.indexer.position), null, null, expr.indexer.position)
|
||||
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
|
||||
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
|
||||
return modifications
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ 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
|
||||
@ -48,7 +49,8 @@ data class CompilationOptions(val output: OutputType,
|
||||
val zeropage: ZeropageType,
|
||||
val zpReserved: List<IntRange>,
|
||||
val floats: Boolean,
|
||||
val noSysInit: Boolean) {
|
||||
val noSysInit: Boolean,
|
||||
val compTarget: ICompilationTarget) {
|
||||
var slowCodegenWarnings = false
|
||||
var optimize = false
|
||||
}
|
||||
@ -59,6 +61,7 @@ class CompilerException(message: String?) : Exception(message)
|
||||
class CompilationResult(val success: Boolean,
|
||||
val programAst: Program,
|
||||
val programName: String,
|
||||
val compTarget: ICompilationTarget,
|
||||
val importedFiles: List<Path>)
|
||||
|
||||
|
||||
@ -73,27 +76,28 @@ fun compileProgram(filepath: Path,
|
||||
lateinit var importedFiles: List<Path>
|
||||
val errors = ErrorReporter()
|
||||
|
||||
when(compilationTarget) {
|
||||
C64Target.name -> ICompilationTarget.instance = C64Target
|
||||
Cx16Target.name -> ICompilationTarget.instance = Cx16Target
|
||||
else -> {
|
||||
System.err.println("invalid compilation target")
|
||||
exitProcess(1)
|
||||
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)
|
||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget)
|
||||
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
|
||||
compilationOptions.optimize = optimize
|
||||
programAst = ast
|
||||
importedFiles = imported
|
||||
processAst(programAst, errors, compilationOptions, ICompilationTarget.instance)
|
||||
processAst(programAst, errors, compilationOptions)
|
||||
if (compilationOptions.optimize)
|
||||
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions))
|
||||
postprocessAst(programAst, errors, compilationOptions, ICompilationTarget.instance)
|
||||
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget, compilationOptions)
|
||||
postprocessAst(programAst, errors, compilationOptions)
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
@ -103,7 +107,7 @@ fun compileProgram(filepath: Path,
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||
return CompilationResult(true, programAst, programName, importedFiles)
|
||||
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
|
||||
|
||||
} catch (px: ParsingFailedError) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
@ -127,8 +131,8 @@ fun compileProgram(filepath: Path,
|
||||
throw x
|
||||
}
|
||||
|
||||
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions))
|
||||
return CompilationResult(false, failedProgram, programName, emptyList())
|
||||
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
|
||||
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
|
||||
}
|
||||
|
||||
private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions {
|
||||
@ -137,13 +141,13 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
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): NumericLiteralValue? {
|
||||
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)
|
||||
exprfunc(args, position, program, memsizer)
|
||||
} catch(x: NotConstArgumentException) {
|
||||
// const-evaluating the builtin function call failed.
|
||||
null
|
||||
@ -153,7 +157,7 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
}
|
||||
}
|
||||
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 // builtin function $name can't be used here because it doesn't return a value
|
||||
}
|
||||
return null
|
||||
}
|
||||
@ -161,34 +165,34 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
builtinFunctionReturnType(name, args, program)
|
||||
}
|
||||
|
||||
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
|
||||
val compilationTargetName = ICompilationTarget.instance.name
|
||||
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)
|
||||
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
|
||||
bf.program = programAst
|
||||
importer.importModule(programAst, filepath, ICompilationTarget.instance, compilationTargetName)
|
||||
errors.handle()
|
||||
importer.importModule(programAst, filepath, compTarget, compilationTargetName)
|
||||
errors.report()
|
||||
|
||||
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
|
||||
|
||||
val compilerOptions = determineCompilationOptions(programAst)
|
||||
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
|
||||
ICompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst, ICompilationTarget.instance, compilationTargetName)
|
||||
for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName))
|
||||
importer.importLibraryModule(programAst, lib, compTarget, compilationTargetName)
|
||||
|
||||
// always import prog8_lib and math
|
||||
importer.importLibraryModule(programAst, "math", ICompilationTarget.instance, compilationTargetName)
|
||||
importer.importLibraryModule(programAst, "prog8_lib", ICompilationTarget.instance, compilationTargetName)
|
||||
errors.handle()
|
||||
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): CompilationOptions {
|
||||
val mainModule = program.modules.first()
|
||||
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" }
|
||||
@ -209,8 +213,8 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
// error will be printed by the astchecker
|
||||
}
|
||||
|
||||
if (zpType==ZeropageType.FLOATSAFE && ICompilationTarget.instance.name == Cx16Target.name) {
|
||||
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
|
||||
if (zpType==ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
|
||||
System.err.println("Warning: zp option floatsafe changed to basicsafe for cx16 target")
|
||||
zpType = ZeropageType.BASICSAFE
|
||||
}
|
||||
|
||||
@ -233,78 +237,95 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
return CompilationOptions(
|
||||
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
||||
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
||||
zpType, zpReserved, floatsEnabled, noSysInit
|
||||
zpType, zpReserved, floatsEnabled, noSysInit,
|
||||
compTarget
|
||||
)
|
||||
}
|
||||
|
||||
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions, compTarget: ICompilationTarget) {
|
||||
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
// perform initial syntax checks and processings
|
||||
println("Processing for target ${compTarget.name}...")
|
||||
programAst.checkIdentifiers(errors, compTarget)
|
||||
errors.handle()
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
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.handle()
|
||||
errors.report()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors, compTarget)
|
||||
errors.handle()
|
||||
programAst.checkIdentifiers(errors, compTarget)
|
||||
errors.handle()
|
||||
errors.report()
|
||||
programAst.variousCleanups(programAst, errors)
|
||||
errors.report()
|
||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
}
|
||||
|
||||
private fun optimizeAst(programAst: Program, errors: ErrorReporter, functions: IBuiltinFunctions) {
|
||||
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget, options: CompilationOptions) {
|
||||
// optimize the parse tree
|
||||
println("Optimizing...")
|
||||
|
||||
val remover = UnusedCodeRemover(programAst, errors, compTarget)
|
||||
remover.visit(programAst)
|
||||
remover.applyModifications()
|
||||
|
||||
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, functions)
|
||||
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
errors.handle()
|
||||
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
|
||||
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget)
|
||||
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)
|
||||
remover.visit(programAst)
|
||||
remover.applyModifications()
|
||||
errors.handle()
|
||||
val inliner = SubroutineInliner(programAst, errors, options)
|
||||
inliner.visit(programAst)
|
||||
errors.report()
|
||||
if(errors.noErrors()) {
|
||||
inliner.applyModifications()
|
||||
inliner.fixCallsToInlinedSubroutines()
|
||||
val remover2 = UnusedCodeRemover(programAst, errors, compTarget)
|
||||
remover2.visit(programAst)
|
||||
remover2.applyModifications()
|
||||
}
|
||||
|
||||
errors.report()
|
||||
}
|
||||
|
||||
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions, compTarget: ICompilationTarget) {
|
||||
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors, compTarget) // check if final tree is still valid
|
||||
errors.handle()
|
||||
errors.report()
|
||||
programAst.variousCleanups(programAst, errors)
|
||||
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
|
||||
errors.report()
|
||||
val callGraph = CallGraph(programAst)
|
||||
callGraph.checkRecursiveCalls(errors)
|
||||
errors.handle()
|
||||
errors.report()
|
||||
programAst.verifyFunctionArgTypes()
|
||||
programAst.moveMainAndStartToFirst()
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
|
||||
private fun writeAssembly(programAst: Program,
|
||||
errors: IErrorReporter,
|
||||
outputDir: Path,
|
||||
compilerOptions: CompilationOptions): String {
|
||||
// asm generation directly from the Ast,
|
||||
programAst.processAstBeforeAsmGeneration(errors, ICompilationTarget.instance)
|
||||
errors.handle()
|
||||
// asm generation directly from the Ast
|
||||
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
|
||||
errors.report()
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
ICompilationTarget.instance.machine.initializeZeropage(compilerOptions)
|
||||
val assembly = asmGeneratorFor(ICompilationTarget.instance,
|
||||
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
|
||||
val assembly = asmGeneratorFor(compilerOptions.compTarget,
|
||||
programAst,
|
||||
errors,
|
||||
ICompilationTarget.instance.machine.zeropage,
|
||||
compilerOptions.compTarget.machine.zeropage,
|
||||
compilerOptions,
|
||||
outputDir).compileToAssembly()
|
||||
assembly.assemble(compilerOptions)
|
||||
errors.handle()
|
||||
errors.report()
|
||||
return assembly.name
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,16 @@ package prog8.compiler
|
||||
import prog8.ast.base.Position
|
||||
import prog8.parser.ParsingFailedError
|
||||
|
||||
class ErrorReporter {
|
||||
|
||||
interface IErrorReporter {
|
||||
fun err(msg: String, position: Position)
|
||||
fun warn(msg: String, position: Position)
|
||||
fun noErrors(): Boolean
|
||||
fun report()
|
||||
}
|
||||
|
||||
|
||||
internal class ErrorReporter: IErrorReporter {
|
||||
private enum class MessageSeverity {
|
||||
WARNING,
|
||||
ERROR
|
||||
@ -13,10 +22,14 @@ class ErrorReporter {
|
||||
private val messages = mutableListOf<CompilerMessage>()
|
||||
private val alreadyReportedMessages = mutableSetOf<String>()
|
||||
|
||||
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
|
||||
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
|
||||
override fun err(msg: String, position: 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 numWarnings = 0
|
||||
messages.forEach {
|
||||
@ -40,5 +53,5 @@ class ErrorReporter {
|
||||
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
||||
}
|
||||
|
||||
fun isEmpty() = messages.isEmpty()
|
||||
override fun noErrors() = messages.none { it.severity==MessageSeverity.ERROR }
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
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"}
|
||||
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
|
@ -8,7 +8,8 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.ErrorReporter
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.ZeropageType
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compiler.functions.builtinFunctionReturnType
|
||||
import prog8.compiler.target.C64Target
|
||||
@ -18,7 +19,7 @@ import java.io.File
|
||||
|
||||
internal class AstChecker(private val program: Program,
|
||||
private val compilerOptions: CompilationOptions,
|
||||
private val errors: ErrorReporter,
|
||||
private val errors: IErrorReporter,
|
||||
private val compTarget: ICompilationTarget
|
||||
) : IAstVisitor {
|
||||
|
||||
@ -41,17 +42,9 @@ 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)
|
||||
}
|
||||
if(compilerOptions.floats) {
|
||||
if (compilerOptions.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
errors.err("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'", program.mainModule.position)
|
||||
}
|
||||
|
||||
super.visit(program)
|
||||
@ -83,7 +76,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(expectedReturnValues.size==1 && returnStmt.value!=null) {
|
||||
val valueDt = returnStmt.value!!.inferType(program)
|
||||
if(!valueDt.isKnown) {
|
||||
errors.err("return value type mismatch", returnStmt.value!!.position)
|
||||
errors.err("return value type mismatch or unknown symbol", returnStmt.value!!.position)
|
||||
} else {
|
||||
if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.STRUCT))
|
||||
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}", returnStmt.value!!.position)
|
||||
@ -131,7 +124,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
else -> errors.err("loop variable must be numeric type", forLoop.position)
|
||||
}
|
||||
if(errors.isEmpty()) {
|
||||
if(errors.noErrors()) {
|
||||
// check loop range values
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
@ -187,7 +180,7 @@ internal class AstChecker(private val program: Program,
|
||||
else -> false
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -216,13 +209,6 @@ internal class AstChecker(private val program: Program,
|
||||
if(uniqueNames.size!=subroutine.parameters.size)
|
||||
err("parameter names must be unique")
|
||||
|
||||
if(subroutine.inline) {
|
||||
if (subroutine.containsDefinedVariables())
|
||||
err("can't inline a subroutine that defines variables")
|
||||
if (!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty())
|
||||
err("can't inline a non-asm subroutine that has parameters")
|
||||
}
|
||||
|
||||
super.visit(subroutine)
|
||||
|
||||
// user-defined subroutines can only have zero or one return type
|
||||
@ -231,13 +217,13 @@ internal class AstChecker(private val program: Program,
|
||||
err("subroutines can only have one return value")
|
||||
|
||||
// subroutine must contain at least one 'return' or 'goto'
|
||||
// (or if it has an asm block, that must contain a 'rts' or 'jmp')
|
||||
// (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
|
||||
if(subroutine.statements.count { it is Return || it is Jump } == 0) {
|
||||
if (subroutine.amountOfRtsInAsm() == 0) {
|
||||
if (subroutine.returntypes.isNotEmpty()) {
|
||||
// for asm subroutines with an address, no statement check is possible.
|
||||
if (subroutine.asmAddress == null && !subroutine.inline)
|
||||
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)")
|
||||
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or rts/jmp/bra in case of %asm)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -351,10 +337,6 @@ internal class AstChecker(private val program: Program,
|
||||
if(statusFlagsNoCarry.isNotEmpty())
|
||||
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 {
|
||||
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
|
||||
// Instead, their reference (address) should be passed (as an UWORD).
|
||||
@ -391,7 +373,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(!idt.isKnown) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
if(stmt.returntypes.size <= 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(DataType.BYTE)) {
|
||||
if(stmt.returntypes.isEmpty() || (stmt.returntypes.size == 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(DataType.BYTE))) {
|
||||
errors.err("return type mismatch", assignment.value.position)
|
||||
}
|
||||
}
|
||||
@ -510,7 +492,7 @@ internal class AstChecker(private val program: Program,
|
||||
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true)
|
||||
err("recursive var declaration")
|
||||
|
||||
// CONST can only occur on simple types (byte, word, float)
|
||||
@ -756,7 +738,7 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
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)")
|
||||
}
|
||||
"%target" -> {
|
||||
@ -970,6 +952,20 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// functions that don't return a value, can't be used in an expression or assignment
|
||||
if(targetStatement is Subroutine) {
|
||||
if(targetStatement.returntypes.isEmpty()) {
|
||||
if(functionCall.parent is Expression || functionCall.parent is Assignment)
|
||||
errors.err("subroutine doesn't return a value", functionCall.position)
|
||||
}
|
||||
}
|
||||
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
|
||||
if(builtinFunctionReturnType(targetStatement.name, functionCall.args, program).isUnknown) {
|
||||
if(functionCall.parent is Expression || functionCall.parent is Assignment)
|
||||
errors.err("function doesn't return a value", functionCall.position)
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(functionCall)
|
||||
}
|
||||
|
||||
@ -1128,15 +1124,9 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
|
||||
|
||||
// check index value 0..255
|
||||
val dtxNum = arrayIndexedExpression.indexer.indexNum?.inferType(program)?.typeOrElse(DataType.STRUCT)
|
||||
if(dtxNum!=null && dtxNum != DataType.UBYTE && dtxNum != DataType.BYTE)
|
||||
val dtxNum = arrayIndexedExpression.indexer.indexExpr.inferType(program)
|
||||
if(!dtxNum.istype(DataType.UBYTE) && !dtxNum.istype(DataType.BYTE))
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
val dtxVar = arrayIndexedExpression.indexer.indexVar?.inferType(program)?.typeOrElse(DataType.STRUCT)
|
||||
if(dtxVar!=null && dtxVar != DataType.UBYTE && dtxVar != DataType.BYTE)
|
||||
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
|
||||
|
||||
if(arrayIndexedExpression.indexer.origExpression!=null)
|
||||
throw FatalAstException("array indexer should have been replaced with a temp var @ ${arrayIndexedExpression.indexer.position}")
|
||||
|
||||
super.visit(arrayIndexedExpression)
|
||||
}
|
||||
@ -1145,12 +1135,18 @@ internal class AstChecker(private val program: Program,
|
||||
val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(conditionType !in IntegerDatatypes)
|
||||
errors.err("when condition must be an integer value", whenStatement.position)
|
||||
val choiceValues = whenStatement.choiceValues(program)
|
||||
val occurringValues = choiceValues.map {it.first}
|
||||
val tally = choiceValues.associate { it.second to occurringValues.count { ov->it.first==ov} }
|
||||
tally.filter { it.value>1 }.forEach {
|
||||
errors.err("choice value occurs multiple times", it.key.position)
|
||||
val tally = mutableSetOf<Int>()
|
||||
for((choices, choiceNode) in whenStatement.choiceValues(program)) {
|
||||
if(choices!=null) {
|
||||
for (c in choices) {
|
||||
if(c in tally)
|
||||
errors.err("choice value already occurs earlier", choiceNode.position)
|
||||
else
|
||||
tally.add(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(whenStatement.choices.isEmpty())
|
||||
errors.err("empty when statement", whenStatement.position)
|
||||
|
||||
@ -1361,7 +1357,7 @@ internal class AstChecker(private val program: Program,
|
||||
val array = value.value.map {
|
||||
when (it) {
|
||||
is NumericLiteralValue -> it.number.toInt()
|
||||
is AddressOf -> it.identifier.heapId(program.namespace)
|
||||
is AddressOf -> it.identifier.hashCode() and 0xffff
|
||||
is TypecastExpression -> {
|
||||
val constVal = it.expression.constValue(program)
|
||||
val cast = constVal?.cast(it.type)
|
||||
@ -1420,7 +1416,10 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
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)
|
||||
|
@ -1,32 +1,39 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.ErrorReporter
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.statements.Directive
|
||||
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, compTarget: ICompilationTarget) {
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
val checker = AstChecker(this, compilerOptions, errors, compTarget)
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter, compTarget: ICompilationTarget) {
|
||||
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
|
||||
fixer.visit(this)
|
||||
fixer.applyModifications()
|
||||
while(errors.noErrors() && fixer.applyModifications()>0) {
|
||||
fixer.visit(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements(errors: ErrorReporter) {
|
||||
internal fun Program.reorderStatements(errors: IErrorReporter) {
|
||||
val reorder = StatementReorderer(this, errors)
|
||||
reorder.visit(this)
|
||||
reorder.applyModifications()
|
||||
if(errors.noErrors()) {
|
||||
reorder.applyModifications()
|
||||
reorder.visit(this)
|
||||
if(errors.noErrors())
|
||||
reorder.applyModifications()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
internal fun Program.addTypecasts(errors: IErrorReporter) {
|
||||
val caster = TypecastsAdder(this, errors)
|
||||
caster.visit(this)
|
||||
caster.applyModifications()
|
||||
@ -37,12 +44,12 @@ internal fun Program.verifyFunctionArgTypes() {
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: ErrorReporter, compTarget: ICompilationTarget) {
|
||||
internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
|
||||
val checker2 = AstIdentifiersChecker(this, errors, compTarget)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.isEmpty()) {
|
||||
if(errors.noErrors()) {
|
||||
val transforms = AstVariousTransforms(this)
|
||||
transforms.visit(this)
|
||||
transforms.applyModifications()
|
||||
@ -56,12 +63,14 @@ internal fun Program.checkIdentifiers(errors: ErrorReporter, compTarget: ICompil
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.variousCleanups() {
|
||||
val process = VariousCleanups()
|
||||
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) {
|
||||
val process = VariousCleanups(program, errors)
|
||||
process.visit(this)
|
||||
process.applyModifications()
|
||||
if(errors.noErrors())
|
||||
process.applyModifications()
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.moveMainAndStartToFirst() {
|
||||
// the module containing the program entrypoint is moved to the first in the sequence.
|
||||
// the "main" block containing the entrypoint is moved to the top in there,
|
||||
@ -69,29 +78,27 @@ internal fun Program.moveMainAndStartToFirst() {
|
||||
|
||||
val directives = modules[0].statements.filterIsInstance<Directive>()
|
||||
val start = this.entrypoint()
|
||||
if(start!=null) {
|
||||
val mod = start.definingModule()
|
||||
val block = start.definingBlock()
|
||||
if(!modules.remove(mod))
|
||||
throw FatalAstException("module wrong")
|
||||
modules.add(0, mod)
|
||||
mod.remove(block)
|
||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
mod.statements.add(block)
|
||||
else
|
||||
mod.statements.add(afterDirective, block)
|
||||
block.remove(start)
|
||||
afterDirective = block.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
block.statements.add(start)
|
||||
else
|
||||
block.statements.add(afterDirective, start)
|
||||
val mod = start.definingModule()
|
||||
val block = start.definingBlock()
|
||||
if(!modules.remove(mod))
|
||||
throw FatalAstException("module wrong")
|
||||
modules.add(0, mod)
|
||||
mod.remove(block)
|
||||
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
mod.statements.add(block)
|
||||
else
|
||||
mod.statements.add(afterDirective, block)
|
||||
block.remove(start)
|
||||
afterDirective = block.statements.indexOfFirst { it !is Directive }
|
||||
if(afterDirective<0)
|
||||
block.statements.add(start)
|
||||
else
|
||||
block.statements.add(afterDirective, start)
|
||||
|
||||
// overwrite the directives in the module containing the entrypoint
|
||||
for(directive in directives) {
|
||||
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
|
||||
modules[0].statements.add(0, directive)
|
||||
}
|
||||
// overwrite the directives in the module containing the entrypoint
|
||||
for(directive in directives) {
|
||||
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
|
||||
modules[0].statements.add(0, directive)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package prog8.compiler.astprocessing
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.compiler.ErrorReporter
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.ArrayLiteralValue
|
||||
@ -11,10 +10,11 @@ import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
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, private val compTarget: ICompilationTarget) : IAstVisitor {
|
||||
internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
|
||||
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||
|
@ -14,7 +14,6 @@ import prog8.ast.walk.IAstModification
|
||||
|
||||
|
||||
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// is it a struct variable? then define all its struct members as mangled names,
|
||||
@ -23,16 +22,14 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
|
||||
val decls = decl.flattenStructMembers()
|
||||
decls.add(decl)
|
||||
val result = AnonymousScope(decls, decl.position)
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
decl, result, parent
|
||||
))
|
||||
return listOf(IAstModification.ReplaceNode(decl, result, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
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).
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
|
@ -13,17 +13,13 @@ import prog8.ast.walk.IAstModification
|
||||
|
||||
|
||||
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
|
||||
// replace the literal string by a identifier reference to a new local vardecl
|
||||
val vardecl = VarDecl.createAuto(string)
|
||||
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(string, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl, string.definingScope())
|
||||
)
|
||||
// replace the literal string by a identifier reference to the interned string
|
||||
val scopedName = program.internString(string)
|
||||
val identifier = IdentifierReference(scopedName, string.position)
|
||||
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
@ -6,11 +6,11 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.ErrorReporter
|
||||
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.
|
||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||
// - library blocks are put last.
|
||||
@ -23,7 +23,6 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
// - sorts the choices in when statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||
|
||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
@ -89,8 +88,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
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)
|
||||
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, 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)
|
||||
@ -102,23 +100,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
}
|
||||
}
|
||||
|
||||
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
arrayIndexedExpression.indexer.indexNum = expr2
|
||||
arrayIndexedExpression.indexer.origExpression = null
|
||||
return noModifications
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
arrayIndexedExpression.indexer.indexVar = expr2
|
||||
arrayIndexedExpression.indexer.origExpression = null
|
||||
return noModifications
|
||||
}
|
||||
is Expression -> {
|
||||
// replace complex indexing with a temp variable
|
||||
return getAutoIndexerVarFor(arrayIndexedExpression)
|
||||
}
|
||||
else -> return noModifications
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
@ -167,41 +149,41 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
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
|
||||
}
|
||||
|
||||
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val subroutine = expr.definingSubroutine()!!
|
||||
val statement = expr.containingStatement()
|
||||
val indexerVarPrefix = "prog8_autovar_index_"
|
||||
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
|
||||
|
||||
// TODO make this a bit smarter so it can reuse indexer variables. BUT BEWARE of scoping+initialization problems then
|
||||
// add another loop index var to be used for this expression
|
||||
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
|
||||
val indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer)
|
||||
repo.add(indexerVar)
|
||||
// create the indexer var at block level scope
|
||||
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
|
||||
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
|
||||
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
|
||||
|
||||
// replace the indexer with just the variable
|
||||
// assign the indexing expression to the helper variable, but only if that hasn't been done already
|
||||
val indexerExpression = expr.indexer.origExpression!!
|
||||
val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
|
||||
val assign = Assignment(target, indexerExpression, indexerExpression.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
|
||||
modifications.add(IAstModification.SetExpression( {
|
||||
expr.indexer.indexVar = it as IdentifierReference
|
||||
expr.indexer.indexNum = null
|
||||
expr.indexer.origExpression = null
|
||||
}, target.identifier!!.copy(), expr.indexer))
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||
it.first?.first() ?: Int.MAX_VALUE
|
||||
@ -216,18 +198,15 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
val declConstValue = declValue.constValue(program)
|
||||
if(declConstValue==null) {
|
||||
// move the vardecl (without value) to the scope and replace this with a regular assignment
|
||||
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
|
||||
if(decl.datatype!=DataType.FLOAT) {
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, declValue, decl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, assign, parent),
|
||||
IAstModification.InsertFirst(decl, decl.definingScope())
|
||||
)
|
||||
}
|
||||
// move the vardecl (without value) to the scope of the defining subroutine and put a regular assignment in its place here.
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, declValue, decl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, assign, parent),
|
||||
IAstModification.InsertFirst(decl, decl.definingSubroutine() as INameScope)
|
||||
)
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -236,32 +215,23 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val valueType = assignment.value.inferType(program)
|
||||
val targetType = assignment.target.inferType(program)
|
||||
var assignments = emptyList<Assignment>()
|
||||
|
||||
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
|
||||
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
|
||||
if (assignment.value is ArrayLiteralValue) {
|
||||
errors.err("cannot assign array literal here, use separate assignment per field", assignment.position)
|
||||
} else {
|
||||
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
|
||||
return copyStructValue(assignment)
|
||||
}
|
||||
}
|
||||
|
||||
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
|
||||
assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
|
||||
if (assignment.value is ArrayLiteralValue) {
|
||||
errors.err("cannot assign array literal here, use separate assignment per element", assignment.position)
|
||||
} else {
|
||||
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
|
||||
return copyArrayValue(assignment)
|
||||
}
|
||||
}
|
||||
|
||||
if(assignments.isNotEmpty()) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
val scope = assignment.definingScope()
|
||||
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
|
||||
modifications.add(IAstModification.Remove(assignment, scope))
|
||||
return modifications
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -314,113 +284,98 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
|
||||
private fun copyArrayValue(assign: Assignment): List<IAstModification> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program)!!
|
||||
val alv = assign.value as? ArrayLiteralValue
|
||||
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||
}
|
||||
|
||||
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
|
||||
val identifier = assign.target.identifier!!
|
||||
val targetVar = identifier.targetVarDecl(program)!!
|
||||
if(targetVar.arraysize==null)
|
||||
errors.err("array has no defined size", assign.position)
|
||||
|
||||
if(assign.value !is IdentifierReference) {
|
||||
errors.err("invalid array value to assign to other array", assign.value.position)
|
||||
return noModifications
|
||||
}
|
||||
val sourceIdent = assign.value as IdentifierReference
|
||||
val sourceVar = sourceIdent.targetVarDecl(program)!!
|
||||
if(!sourceVar.isArray) {
|
||||
errors.err("value must be an array", sourceIdent.position)
|
||||
return emptyList()
|
||||
errors.err("value must be an array", sourceIdent.position)
|
||||
} else {
|
||||
if (sourceVar.arraysize!!.constIndex() != targetVar.arraysize!!.constIndex())
|
||||
errors.err("element count mismatch", assign.position)
|
||||
if (sourceVar.datatype != targetVar.datatype)
|
||||
errors.err("element type mismatch", assign.position)
|
||||
}
|
||||
val alv = sourceVar.value as? ArrayLiteralValue
|
||||
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
|
||||
|
||||
if(!errors.noErrors())
|
||||
return noModifications
|
||||
|
||||
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
|
||||
mutableListOf(
|
||||
AddressOf(sourceIdent, assign.position),
|
||||
AddressOf(identifier, assign.position),
|
||||
NumericLiteralValue.optimalInteger(targetVar.arraysize!!.constIndex()!!, assign.position)
|
||||
),
|
||||
true,
|
||||
assign.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
|
||||
}
|
||||
|
||||
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
|
||||
if(targetVar.arraysize==null) {
|
||||
errors.err("array has no defined size", identifier.position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
|
||||
errors.err("element count mismatch", position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
// TODO use a pointer loop instead of individual assignments
|
||||
return alv.value.mapIndexed { index, value ->
|
||||
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
|
||||
Assignment(AssignTarget(null, idx, null, position), value, value.position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
|
||||
private fun copyStructValue(structAssignment: Assignment): List<IAstModification> {
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program)!!
|
||||
val struct = targetVar.struct!!
|
||||
|
||||
val slv = structAssignment.value as? ArrayLiteralValue
|
||||
if(slv==null || slv.value.size != struct.numberOfElements) {
|
||||
errors.err("element count mismatch", structAssignment.position)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
|
||||
targetDecl as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
|
||||
sourceValue, sourceValue.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
|
||||
// TODO use memcopy beyond a certain number of elements
|
||||
val identifier = structAssignment.target.identifier!!
|
||||
val identifierName = identifier.nameInSource.single()
|
||||
val targetVar = identifier.targetVarDecl(program)!!
|
||||
val struct = targetVar.struct!!
|
||||
when (structAssignment.value) {
|
||||
is IdentifierReference -> {
|
||||
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program)!!
|
||||
val memsize = struct.memsize(program.memsizer)
|
||||
when {
|
||||
sourceVar.struct!=null -> {
|
||||
// struct memberwise copy
|
||||
val sourceStruct = sourceVar.struct!!
|
||||
if(sourceStruct!==targetVar.struct) {
|
||||
// structs are not the same in assignment
|
||||
return listOf() // error will be printed elsewhere
|
||||
errors.err("struct type mismatch", structAssignment.position)
|
||||
return listOf()
|
||||
}
|
||||
if(struct.statements.size!=sourceStruct.statements.size)
|
||||
return listOf() // error will be printed elsewhere
|
||||
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||
val targetDecl = member.first as VarDecl
|
||||
val sourceDecl = member.second as VarDecl
|
||||
if(targetDecl.name != sourceDecl.name)
|
||||
throw FatalAstException("struct member mismatch")
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
if(struct.statements.size!=sourceStruct.statements.size) {
|
||||
errors.err("struct element count mismatch", structAssignment.position)
|
||||
return listOf()
|
||||
}
|
||||
if(memsize!=sourceStruct.memsize(program.memsizer)) {
|
||||
errors.err("memory size mismatch", structAssignment.position)
|
||||
return listOf()
|
||||
}
|
||||
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
|
||||
mutableListOf(
|
||||
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
|
||||
AddressOf(identifier, structAssignment.position),
|
||||
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
|
||||
),
|
||||
true,
|
||||
structAssignment.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
|
||||
}
|
||||
sourceVar.isArray -> {
|
||||
val array = (sourceVar.value as ArrayLiteralValue).value
|
||||
if(struct.statements.size!=array.size)
|
||||
return listOf() // error will be printed elsewhere
|
||||
return struct.statements.zip(array).map {
|
||||
val decl = it.first as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, decl.name)
|
||||
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val target = AssignTarget(targetName, null, null, structAssignment.position)
|
||||
val assign = Assignment(target, it.second, structAssignment.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
val array = sourceVar.value as ArrayLiteralValue
|
||||
if(struct.statements.size!=array.value.size) {
|
||||
errors.err("struct element count mismatch", structAssignment.position)
|
||||
return listOf()
|
||||
}
|
||||
if(memsize!=array.memsize(program.memsizer)) {
|
||||
errors.err("memory size mismatch", structAssignment.position)
|
||||
return listOf()
|
||||
}
|
||||
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
|
||||
mutableListOf(
|
||||
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
|
||||
AddressOf(identifier, structAssignment.position),
|
||||
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
|
||||
),
|
||||
true,
|
||||
structAssignment.position
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("can only assign arrays or structs to structs")
|
||||
@ -428,7 +383,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
|
||||
}
|
||||
}
|
||||
is ArrayLiteralValue -> {
|
||||
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||
throw IllegalArgumentException("not going to do a structLv assignment here")
|
||||
}
|
||||
else -> throw FatalAstException("strange struct value")
|
||||
}
|
||||
|
@ -8,18 +8,16 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.ErrorReporter
|
||||
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.
|
||||
* (this includes function call arguments)
|
||||
*/
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val declValue = decl.value
|
||||
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
|
||||
|
@ -3,18 +3,17 @@ package prog8.compiler.astprocessing
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
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.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.IErrorReporter
|
||||
|
||||
|
||||
internal class VariousCleanups: AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() {
|
||||
|
||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
|
||||
@ -32,21 +31,12 @@ internal class VariousCleanups: AstWalker() {
|
||||
val idx = into.statements.indexOf(scope)
|
||||
if(idx>=0) {
|
||||
into.statements.addAll(idx+1, scope.statements)
|
||||
scope.statements.forEach { it.parent = into as Node }
|
||||
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)
|
||||
}
|
||||
@ -70,4 +60,62 @@ internal class VariousCleanups: AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $typecast")
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
val sourceDt = typecast.expression.inferType(program)
|
||||
if(sourceDt.istype(typecast.type))
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
if(subroutine.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $subroutine")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $assignment")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> {
|
||||
if(assignTarget.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $assignTarget")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $decl")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
if(scope.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $scope")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
if(returnStmt.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $returnStmt")
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
if(identifier.parent!==parent)
|
||||
throw FatalAstException("parent node mismatch at $identifier")
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
package prog8.compiler.functions
|
||||
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.StructDecl
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import kotlin.math.*
|
||||
|
||||
|
||||
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)
|
||||
@ -88,6 +88,7 @@ class FSignature(val name: String,
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
|
||||
private val functionSignatures: List<FSignature> = listOf(
|
||||
// this set of function have no return value and operate in-place:
|
||||
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
@ -96,46 +97,48 @@ private val functionSignatures: List<FSignature> = listOf(
|
||||
FSignature("ror2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
|
||||
// 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("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> 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("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, ct -> collectionArg(a, p, prg, ::builtinMin) }, // 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("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("offsetof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinOffsetof),
|
||||
// normal functions follow:
|
||||
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("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||
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("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("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||
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("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> 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("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> 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("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> 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("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> 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("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> 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("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> 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("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("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, ct -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
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, ct -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
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, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
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, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
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, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
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, ct -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
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, 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, 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("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("fastrnd8" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
|
||||
FSignature("rndw" , false, emptyList(), DataType.UWORD),
|
||||
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
|
||||
@ -271,7 +274,8 @@ private fun collectionArg(args: List<Expression>, position: Position, program: P
|
||||
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
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("abs requires one numeric argument", position)
|
||||
@ -284,7 +288,7 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinOffsetof(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)
|
||||
@ -301,12 +305,12 @@ private fun builtinOffsetof(args: List<Expression>, position: Position, program:
|
||||
for(member in struct.statements) {
|
||||
if((member as VarDecl).name == membername)
|
||||
return NumericLiteralValue(DataType.UBYTE, offset, position)
|
||||
offset += ICompilationTarget.instance.memorySize(member.datatype)
|
||||
offset += memsizer.memorySize(member.datatype)
|
||||
}
|
||||
throw SyntaxError("undefined struct member", position)
|
||||
}
|
||||
|
||||
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
|
||||
// 1 arg, type = anything, result type = ubyte
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("sizeof requires one argument", position)
|
||||
@ -318,14 +322,13 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
|
||||
val target = (args[0] as IdentifierReference).targetStatement(program)
|
||||
?: throw CannotEvaluateException("sizeof", "no target")
|
||||
|
||||
fun structSize(target: StructDecl) =
|
||||
NumericLiteralValue(DataType.UBYTE, target.statements.map { ICompilationTarget.instance.memorySize((it as VarDecl).datatype) }.sum(), position)
|
||||
fun structSize(target: StructDecl) = NumericLiteralValue(DataType.UBYTE, target.memsize(memsizer), position)
|
||||
|
||||
return when {
|
||||
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
|
||||
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
||||
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
|
||||
numericLiteral(ICompilationTarget.instance.memorySize(elementDt) * length, position)
|
||||
numericLiteral(memsizer.memorySize(elementDt) * length, position)
|
||||
}
|
||||
dt.istype(DataType.STRUCT) -> {
|
||||
when (target) {
|
||||
@ -335,14 +338,15 @@ 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)
|
||||
else -> NumericLiteralValue(DataType.UBYTE, ICompilationTarget.instance.memorySize(dt.typeOrElse(DataType.STRUCT)), position)
|
||||
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.STRUCT)), position)
|
||||
}
|
||||
} else {
|
||||
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.
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("len requires one argument", position)
|
||||
@ -366,7 +370,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
}
|
||||
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)
|
||||
}
|
||||
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
|
||||
@ -376,7 +380,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)
|
||||
throw SyntaxError("mkword requires msb and lsb arguments", position)
|
||||
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
@ -385,7 +390,8 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
|
||||
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)
|
||||
throw SyntaxError("sin8 requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
@ -393,7 +399,8 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
|
||||
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)
|
||||
throw SyntaxError("sin8u requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
@ -401,7 +408,8 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
|
||||
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)
|
||||
throw SyntaxError("cos8 requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
@ -409,7 +417,8 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
|
||||
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)
|
||||
throw SyntaxError("cos8u requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
@ -417,7 +426,8 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
|
||||
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)
|
||||
throw SyntaxError("sin16 requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
@ -425,7 +435,8 @@ private fun builtinSin16(args: List<Expression>, position: Position, program: Pr
|
||||
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)
|
||||
throw SyntaxError("sin16u requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
@ -433,7 +444,8 @@ private fun builtinSin16u(args: List<Expression>, position: Position, program: P
|
||||
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)
|
||||
throw SyntaxError("cos16 requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
@ -441,7 +453,8 @@ private fun builtinCos16(args: List<Expression>, position: Position, program: Pr
|
||||
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)
|
||||
throw SyntaxError("cos16u requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
@ -449,7 +462,8 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
|
||||
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)
|
||||
throw SyntaxError("sgn requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
|
@ -1,31 +1,29 @@
|
||||
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.AssemblyError
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.ErrorReporter
|
||||
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.cbm.Petscii
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import java.io.CharConversionException
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
internal interface ICompilationTarget: IStringEncoding {
|
||||
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 memorySize(dt: DataType): Int
|
||||
|
||||
companion object {
|
||||
lateinit var instance: ICompilationTarget // TODO reduce dependency on this by just passing the instance as a parameter
|
||||
}
|
||||
|
||||
fun isInRegularRAM(target: AssignTarget, program: Program): Boolean {
|
||||
val memAddr = target.memoryAddress
|
||||
@ -67,7 +65,6 @@ internal interface ICompilationTarget: IStringEncoding {
|
||||
else -> return true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -75,9 +72,17 @@ 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)
|
||||
try {
|
||||
if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
} catch (x: CharConversionException) {
|
||||
throw AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
try {
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
} catch (x: CharConversionException) {
|
||||
throw AssemblyError("There was a problem decoding to a string: ${x.message}")
|
||||
}
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
@ -94,9 +99,17 @@ 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)
|
||||
try {
|
||||
if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
} catch (x: CharConversionException) {
|
||||
throw AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
|
||||
}
|
||||
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
try {
|
||||
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
} catch (x: CharConversionException) {
|
||||
throw AssemblyError("There was a problem decoding to a string: ${x.message}")
|
||||
}
|
||||
|
||||
override fun memorySize(dt: DataType): Int {
|
||||
return when(dt) {
|
||||
@ -113,11 +126,12 @@ internal object Cx16Target: ICompilationTarget {
|
||||
internal fun asmGeneratorFor(
|
||||
compTarget: ICompilationTarget,
|
||||
program: Program,
|
||||
errors: ErrorReporter,
|
||||
errors: IErrorReporter,
|
||||
zp: Zeropage,
|
||||
options: CompilationOptions,
|
||||
outputDir: Path
|
||||
): IAssemblyGenerator
|
||||
{
|
||||
// at the moment we only have one code generation backend (for 6502 and 65c02)
|
||||
return AsmGen(program, errors, zp, options, compTarget, outputDir)
|
||||
}
|
||||
|
@ -1,23 +1,20 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.IStringEncoding
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.parser.ModuleImporter
|
||||
|
||||
|
||||
internal interface IMachineFloat {
|
||||
interface IMachineFloat {
|
||||
fun toDouble(): Double
|
||||
fun makeFloatFillAsm(): String
|
||||
}
|
||||
|
||||
internal enum class CpuType {
|
||||
enum class CpuType {
|
||||
CPU6502,
|
||||
CPU65c02
|
||||
}
|
||||
|
||||
internal interface IMachineDefinition {
|
||||
interface IMachineDefinition {
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: Int
|
||||
@ -34,10 +31,7 @@ internal interface IMachineDefinition {
|
||||
fun initializeZeropage(compilerOptions: CompilationOptions)
|
||||
fun getFloat(num: Number): IMachineFloat
|
||||
|
||||
// TODO don't do the importing here, just return a list of modules to import...:
|
||||
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program,
|
||||
encoder: IStringEncoding, compilationTargetName: String)
|
||||
|
||||
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
|
||||
fun launchEmulator(programName: String)
|
||||
fun isRegularRAMaddress(address: Int): Boolean
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
package prog8.compiler.target.c64
|
||||
|
||||
import prog8.ast.IStringEncoding
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.IMachineDefinition
|
||||
import prog8.compiler.target.IMachineFloat
|
||||
import prog8.parser.ModuleImporter
|
||||
import java.io.IOException
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
@ -31,15 +28,11 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
|
||||
override fun importLibs(
|
||||
compilerOptions: CompilationOptions,
|
||||
importer: ModuleImporter,
|
||||
program: Program,
|
||||
encoder: IStringEncoding,
|
||||
compilationTargetName: String)
|
||||
{
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
importer.importLibraryModule(program, "syslib", encoder, compilationTargetName)
|
||||
override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
@ -109,13 +102,14 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
// 0x90-0xfa is 'kernel work storage area'
|
||||
// 0x90-0xfa is 'kernal work storage area'
|
||||
))
|
||||
}
|
||||
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zero page locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64
|
||||
package prog8.compiler.target.cbm
|
||||
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.OutputType
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64
|
||||
package prog8.compiler.target.cbm
|
||||
|
||||
import java.io.CharConversionException
|
||||
|
||||
@ -172,7 +172,7 @@ object Petscii {
|
||||
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
|
||||
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
|
||||
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
|
||||
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
|
||||
'_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
|
||||
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
|
||||
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
|
||||
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
|
||||
@ -236,7 +236,7 @@ object Petscii {
|
||||
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
|
||||
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
|
||||
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
|
||||
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
|
||||
'_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
|
||||
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
|
||||
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
|
||||
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
|
||||
@ -431,7 +431,7 @@ object Petscii {
|
||||
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
|
||||
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
|
||||
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
|
||||
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
|
||||
'_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
|
||||
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
|
||||
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
|
||||
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
|
||||
@ -495,7 +495,7 @@ object Petscii {
|
||||
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
|
||||
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
|
||||
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
|
||||
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
|
||||
'_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
|
||||
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
|
||||
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
|
||||
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
|
||||
@ -626,7 +626,7 @@ object Petscii {
|
||||
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
|
||||
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
|
||||
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
|
||||
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
|
||||
'_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
|
||||
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
|
||||
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
|
||||
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
|
||||
@ -885,7 +885,7 @@ object Petscii {
|
||||
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
|
||||
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
|
||||
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
|
||||
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
|
||||
'_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
|
||||
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
|
||||
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
|
||||
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
|
||||
@ -1051,49 +1051,75 @@ object Petscii {
|
||||
|
||||
|
||||
fun encodePetscii(text: String, lowercase: Boolean = false): List<Short> {
|
||||
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
|
||||
return text.map {
|
||||
val petscii = lookup[it]
|
||||
petscii?.toShort() ?: when (it) {
|
||||
fun encodeChar(chr: Char, lowercase: Boolean): Short {
|
||||
val screencode = if(lowercase) encodingPetsciiLowercase[chr] else encodingPetsciiUppercase[chr]
|
||||
return screencode?.toShort() ?: when (chr) {
|
||||
'\u0000' -> 0.toShort()
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(it.toInt() - 0x8000).toShort()
|
||||
(chr.toInt() - 0x8000).toShort()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
|
||||
throw CharConversionException("no ${case}Petscii character for '$chr' (${chr.toShort()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text.map{
|
||||
try {
|
||||
encodeChar(it, lowercase)
|
||||
} catch (x: CharConversionException) {
|
||||
encodeChar(it, !lowercase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun decodePetscii(petscii: Iterable<Short>, lowercase: Boolean = false): String {
|
||||
val decodeTable = if(lowercase) decodingPetsciiLowercase else decodingPetsciiUppercase
|
||||
return petscii.map { decodeTable[it.toInt()] }.joinToString("")
|
||||
return petscii.map {
|
||||
val code = it.toInt()
|
||||
try {
|
||||
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
|
||||
} catch(x: CharConversionException) {
|
||||
if(lowercase) decodingPetsciiUppercase[code] else decodingPetsciiLowercase[code]
|
||||
}
|
||||
}.joinToString("")
|
||||
}
|
||||
|
||||
fun encodeScreencode(text: String, lowercase: Boolean = false): List<Short> {
|
||||
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
|
||||
return text.map{
|
||||
val screencode = lookup[it]
|
||||
screencode?.toShort() ?: when (it) {
|
||||
fun encodeChar(chr: Char, lowercase: Boolean): Short {
|
||||
val screencode = if(lowercase) encodingScreencodeLowercase[chr] else encodingScreencodeUppercase[chr]
|
||||
return screencode?.toShort() ?: when (chr) {
|
||||
'\u0000' -> 0.toShort()
|
||||
in '\u8000'..'\u80ff' -> {
|
||||
// special case: take the lower 8 bit hex value directly
|
||||
(it.toInt() - 0x8000).toShort()
|
||||
(chr.toInt() - 0x8000).toShort()
|
||||
}
|
||||
else -> {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
|
||||
throw CharConversionException("no ${case}Screencode character for '$chr' (${chr.toShort()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text.map{
|
||||
try {
|
||||
encodeChar(it, lowercase)
|
||||
} catch (x: CharConversionException) {
|
||||
encodeChar(it, !lowercase)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeScreencode(screencode: Iterable<Short>, lowercase: Boolean = false): String {
|
||||
val decodeTable = if(lowercase) decodingScreencodeLowercase else decodingScreencodeUppercase
|
||||
return screencode.map { decodeTable[it.toInt()] }.joinToString("")
|
||||
return screencode.map {
|
||||
val code = it.toInt()
|
||||
try {
|
||||
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
|
||||
} catch (x: CharConversionException) {
|
||||
if (lowercase) decodingScreencodeUppercase[code] else decodingScreencodeLowercase[code]
|
||||
}
|
||||
}.joinToString("")
|
||||
}
|
||||
|
||||
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Short {
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.antlr.escape
|
||||
@ -9,12 +9,12 @@ import prog8.compiler.*
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compiler.functions.FSignature
|
||||
import prog8.compiler.target.*
|
||||
import prog8.compiler.target.c64.AssemblyProgram
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.c64.codegen.assignment.AssignmentAsmGen
|
||||
import java.io.CharConversionException
|
||||
import prog8.compiler.target.cbm.AssemblyProgram
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
|
||||
import prog8.optimizer.CallGraph
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
@ -22,15 +22,16 @@ import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
internal class AsmGen(private val program: Program,
|
||||
val errors: ErrorReporter,
|
||||
val errors: IErrorReporter,
|
||||
val zeropage: Zeropage,
|
||||
val options: CompilationOptions,
|
||||
val compTarget: ICompilationTarget,
|
||||
private val compTarget: ICompilationTarget,
|
||||
private val outputDir: Path): IAssemblyGenerator {
|
||||
|
||||
// for expressions and augmented assignments:
|
||||
val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
|
||||
val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100,320)
|
||||
private val callGraph = CallGraph(program)
|
||||
|
||||
private val assemblyLines = mutableListOf<String>()
|
||||
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
@ -88,12 +89,14 @@ internal class AsmGen(private val program: Program,
|
||||
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() {
|
||||
val ourName = this.javaClass.name
|
||||
val cpu = when(compTarget.machine.cpu) {
|
||||
CpuType.CPU6502 -> "6502"
|
||||
CpuType.CPU65c02 -> "65c02"
|
||||
CpuType.CPU65c02 -> "w65c02"
|
||||
else -> "unsupported"
|
||||
}
|
||||
|
||||
@ -130,12 +133,14 @@ internal class AsmGen(private val program: Program,
|
||||
out("_prog8_entrypoint\t; assembly code starts here\n")
|
||||
if(!options.noSysInit)
|
||||
out(" jsr ${compTarget.name}.init_system")
|
||||
out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
}
|
||||
options.output == OutputType.PRG -> {
|
||||
out("; ---- program without basic sys call ----")
|
||||
out("* = ${program.actualLoadAddress.toHex()}\n")
|
||||
if(!options.noSysInit)
|
||||
out(" jsr ${compTarget.name}.init_system")
|
||||
out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
}
|
||||
options.output == OutputType.RAW -> {
|
||||
out("; ---- raw assembler program ----")
|
||||
@ -152,7 +157,16 @@ internal class AsmGen(private val program: Program,
|
||||
pha""")
|
||||
}
|
||||
|
||||
out(" jmp main.start ; start program / force start proc to be included")
|
||||
// make sure that on the cx16 and c64, basic rom is banked in again when we exit the program
|
||||
when(compTarget.name) {
|
||||
Cx16Target.name -> {
|
||||
if(options.floats)
|
||||
out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
|
||||
out(" jsr main.start | lda #4 | sta $01 | rts")
|
||||
}
|
||||
C64Target.name -> out(" jsr main.start | lda #31 | sta $01 | rts")
|
||||
else -> jmp("main.start")
|
||||
}
|
||||
}
|
||||
|
||||
private fun slaballocations() {
|
||||
@ -176,14 +190,17 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
private fun block2asm(block: Block) {
|
||||
out("\n\n; ---- block: '${block.name}' ----")
|
||||
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
|
||||
|
||||
val addr = block.address
|
||||
if(addr!=null) {
|
||||
out(".cerror * > ${addr.toHex()}, 'block address overlaps by ', *-${addr.toHex()},' bytes'")
|
||||
out("* = ${addr.toHex()}")
|
||||
if(block.address!=null)
|
||||
out("* = ${block.address!!.toHex()}")
|
||||
else {
|
||||
if("align_word" in block.options())
|
||||
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)
|
||||
zeropagevars2asm(block.statements)
|
||||
memdefs2asm(block.statements)
|
||||
@ -238,15 +255,6 @@ internal class AsmGen(private val program: Program,
|
||||
} else assemblyLines.add(fragment)
|
||||
}
|
||||
|
||||
private fun encode(str: String, altEncoding: Boolean): List<Short> {
|
||||
try {
|
||||
val bytes = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return bytes.plus(0)
|
||||
} catch(x: CharConversionException) {
|
||||
throw AssemblyError("There was a problem converting a string to the target machine's char encoding: ${x.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun zeropagevars2asm(statements: List<Statement>) {
|
||||
out("; vars allocated on zeropage")
|
||||
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||
@ -262,7 +270,7 @@ internal class AsmGen(private val program: Program,
|
||||
try {
|
||||
val errors = ErrorReporter()
|
||||
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
|
||||
errors.handle()
|
||||
errors.report()
|
||||
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
|
||||
// make sure we add the var to the set of zpvars for this block
|
||||
allocatedZeropageVariables[fullName] = address to variable.datatype
|
||||
@ -285,7 +293,7 @@ internal class AsmGen(private val program: Program,
|
||||
DataType.STRUCT -> {} // is flattened
|
||||
DataType.STR -> {
|
||||
val str = decl.value as StringLiteralValue
|
||||
outputStringvar(decl, encode(str.value, str.altEncoding))
|
||||
outputStringvar(decl, compTarget.encodeString(str.value, str.altEncoding).plus(0))
|
||||
}
|
||||
DataType.ARRAY_UB -> {
|
||||
val data = makeArrayFillDataUnsigned(decl)
|
||||
@ -348,7 +356,7 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
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 }
|
||||
for(m in memvars) {
|
||||
if(m.value is NumericLiteralValue)
|
||||
@ -361,7 +369,7 @@ internal class AsmGen(private val program: Program,
|
||||
val addr = sub.asmAddress
|
||||
if(addr!=null) {
|
||||
if(sub.statements.isNotEmpty())
|
||||
throw AssemblyError("kernel subroutine cannot have statements")
|
||||
throw AssemblyError("kernal subroutine cannot have statements")
|
||||
out(" ${sub.name} = ${addr.toHex()}")
|
||||
}
|
||||
}
|
||||
@ -382,7 +390,7 @@ internal class AsmGen(private val program: Program,
|
||||
.filter {it.datatype == DataType.STR }
|
||||
.map {
|
||||
val str = it.value as StringLiteralValue
|
||||
it to encode(str.value, str.altEncoding)
|
||||
it to compTarget.encodeString(str.value, str.altEncoding).plus(0)
|
||||
}
|
||||
.groupBy({it.second}, {it.first})
|
||||
for((encoded, variables) in encodedstringVars) {
|
||||
@ -399,8 +407,9 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
|
||||
val string = (lastvar.value as StringLiteralValue).value
|
||||
out("${lastvar.name}\t; ${lastvar.datatype} \"${escape(string).replace("\u0000", "<NULL>")}\"")
|
||||
val sv = lastvar.value as StringLiteralValue
|
||||
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') }
|
||||
for (chunk in outputBytes.chunked(16))
|
||||
out(" .byte " + chunk.joinToString())
|
||||
@ -527,7 +536,7 @@ internal class AsmGen(private val program: Program,
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
if (compTarget.machine.cpu == CpuType.CPU65c02) {
|
||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||
return if (isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" lda ($sourceName)")
|
||||
@ -562,7 +571,7 @@ internal class AsmGen(private val program: Program,
|
||||
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
||||
|
||||
internal fun saveRegisterLocal(register: CpuRegister, scope: Subroutine) {
|
||||
if (compTarget.machine.cpu == CpuType.CPU65c02) {
|
||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||
// just use the cpu's stack for all registers, shorter code
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
@ -591,7 +600,7 @@ internal class AsmGen(private val program: Program,
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> {
|
||||
if (compTarget.machine.cpu == CpuType.CPU65c02) out(" phx")
|
||||
if (isTargetCpu(CpuType.CPU65c02)) out(" phx")
|
||||
else {
|
||||
if(keepA)
|
||||
out(" sta P8ZP_SCRATCH_REG | txa | pha | lda P8ZP_SCRATCH_REG")
|
||||
@ -600,7 +609,7 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (compTarget.machine.cpu == CpuType.CPU65c02) out(" phy")
|
||||
if (isTargetCpu(CpuType.CPU65c02)) out(" phy")
|
||||
else {
|
||||
if(keepA)
|
||||
out(" sta P8ZP_SCRATCH_REG | tya | pha | lda P8ZP_SCRATCH_REG")
|
||||
@ -612,7 +621,7 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
internal fun restoreRegisterLocal(register: CpuRegister) {
|
||||
if (compTarget.machine.cpu == CpuType.CPU65c02) {
|
||||
if (isTargetCpu(CpuType.CPU65c02)) {
|
||||
when (register) {
|
||||
// this just used the stack, for all registers. Shorter code.
|
||||
CpuRegister.A -> out(" pla")
|
||||
@ -637,7 +646,7 @@ internal class AsmGen(private val program: Program,
|
||||
out(" pla")
|
||||
}
|
||||
CpuRegister.X -> {
|
||||
if (compTarget.machine.cpu == CpuType.CPU65c02) out(" plx")
|
||||
if (isTargetCpu(CpuType.CPU65c02)) out(" plx")
|
||||
else {
|
||||
if(keepA)
|
||||
out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
|
||||
@ -646,7 +655,7 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (compTarget.machine.cpu == CpuType.CPU65c02) out(" ply")
|
||||
if (isTargetCpu(CpuType.CPU65c02)) out(" ply")
|
||||
else {
|
||||
if(keepA)
|
||||
out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
|
||||
@ -686,7 +695,7 @@ internal class AsmGen(private val program: Program,
|
||||
is Break -> {
|
||||
if(loopEndLabels.isEmpty())
|
||||
throw AssemblyError("break statement out of context ${stmt.position}")
|
||||
out(" jmp ${loopEndLabels.peek()}")
|
||||
jmp(loopEndLabels.peek())
|
||||
}
|
||||
is WhileLoop -> translate(stmt)
|
||||
is RepeatLoop -> translate(stmt)
|
||||
@ -711,7 +720,10 @@ internal class AsmGen(private val program: Program,
|
||||
return
|
||||
}
|
||||
|
||||
val indexName = asmVariableName(expr.indexer.indexVar!!)
|
||||
val indexVar = expr.indexer.indexExpr as? IdentifierReference
|
||||
?: throw AssemblyError("array indexer should have been replaced with a temp var @ ${expr.indexer.position}")
|
||||
|
||||
val indexName = asmVariableName(indexVar)
|
||||
if(addOneExtra) {
|
||||
// add 1 to the result
|
||||
when(elementDt) {
|
||||
@ -809,9 +821,18 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
|
||||
private fun translateSubroutine(sub: Subroutine) {
|
||||
var onlyVariables = false
|
||||
|
||||
if(sub.inline) {
|
||||
if(options.optimize)
|
||||
return // inline subroutines don't exist anymore on their own
|
||||
if(options.optimize) {
|
||||
if(sub.isAsmSubroutine ||callGraph.unused(sub))
|
||||
return
|
||||
|
||||
// from an inlined subroutine only the local variables are generated,
|
||||
// all other code statements are omitted in the subroutine itself
|
||||
// (they've been inlined at the call site, remember?)
|
||||
onlyVariables = true
|
||||
}
|
||||
else if(sub.amountOfRtsInAsm()==0) {
|
||||
// make sure the NOT INLINED subroutine actually does an rts at the end
|
||||
sub.statements.add(Return(null, Position.DUMMY))
|
||||
@ -828,7 +849,7 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
// asmsub with most likely just an inline asm in it
|
||||
out("${sub.name}\t.proc")
|
||||
sub.statements.forEach{ translate(it) }
|
||||
sub.statements.forEach { translate(it) }
|
||||
out(" .pend\n")
|
||||
} else {
|
||||
// regular subroutine
|
||||
@ -852,8 +873,10 @@ internal class AsmGen(private val program: Program,
|
||||
clc""")
|
||||
}
|
||||
|
||||
out("; statements")
|
||||
sub.statements.forEach{ translate(it) }
|
||||
if(!onlyVariables) {
|
||||
out("; statements")
|
||||
sub.statements.forEach { translate(it) }
|
||||
}
|
||||
|
||||
for(removal in removals.toList()) {
|
||||
if(removal.second==sub) {
|
||||
@ -908,6 +931,10 @@ internal class AsmGen(private val program: Program,
|
||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||
val booleanCondition = stmt.condition as BinaryExpression
|
||||
|
||||
// DISABLED FOR NOW:
|
||||
// if(!booleanCondition.left.isSimple || !booleanCondition.right.isSimple)
|
||||
// throw AssemblyError("both operands for if comparison expression should have been simplified")
|
||||
|
||||
if (stmt.elsepart.containsNoCodeNorVars()) {
|
||||
// empty else
|
||||
val endLabel = makeLabel("if_end")
|
||||
@ -921,7 +948,7 @@ internal class AsmGen(private val program: Program,
|
||||
val endLabel = makeLabel("if_end")
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, elseLabel)
|
||||
translate(stmt.truepart)
|
||||
out(" jmp $endLabel")
|
||||
jmp(endLabel)
|
||||
out(elseLabel)
|
||||
translate(stmt.elsepart)
|
||||
out(endLabel)
|
||||
@ -943,7 +970,7 @@ internal class AsmGen(private val program: Program,
|
||||
// endless loop
|
||||
out(repeatLabel)
|
||||
translate(stmt.body)
|
||||
out(" jmp $repeatLabel")
|
||||
jmp(repeatLabel)
|
||||
out(endLabel)
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
@ -967,10 +994,12 @@ internal class AsmGen(private val program: Program,
|
||||
val name = asmVariableName(stmt.iterations as IdentifierReference)
|
||||
when(vardecl.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
repeatByteCountVar(name, repeatLabel, endLabel, stmt.body)
|
||||
assignVariableToRegister(name, RegisterOrPair.A)
|
||||
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
repeatWordCountVar(name, repeatLabel, endLabel, stmt.body)
|
||||
assignVariableToRegister(name, RegisterOrPair.AY)
|
||||
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
|
||||
}
|
||||
@ -997,10 +1026,11 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
// note: A/Y must have been loaded with the number of iterations!
|
||||
if(constIterations==0)
|
||||
return
|
||||
// note: A/Y must have been loaded with the number of iterations already!
|
||||
// TODO can be even more optimized by iterating over pages
|
||||
// no need to explicitly test for 0 iterations as this is done in the count down logic below
|
||||
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
out("""
|
||||
sta $counterVar
|
||||
@ -1009,80 +1039,48 @@ $repeatLabel lda $counterVar
|
||||
bne +
|
||||
lda $counterVar+1
|
||||
beq $endLabel
|
||||
+ lda $counterVar
|
||||
lda $counterVar
|
||||
bne +
|
||||
dec $counterVar+1
|
||||
+ dec $counterVar
|
||||
""")
|
||||
translate(body)
|
||||
out(" jmp $repeatLabel")
|
||||
if(constIterations!=null && constIterations>=16 && zeropage.available() > 1) {
|
||||
// allocate count var on ZP
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, body.position, errors)
|
||||
out("""$counterVar = $zpAddr ; auto zp UWORD""")
|
||||
} else {
|
||||
out("""
|
||||
$counterVar .word 0""")
|
||||
}
|
||||
out(endLabel)
|
||||
jmp(repeatLabel)
|
||||
|
||||
if(constIterations!=null && constIterations>=16 && zeropage.available() > 1) {
|
||||
// allocate count var on ZP TODO can be shared with countervars from other subroutines
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, body.position, errors)
|
||||
out("$counterVar = $zpAddr ; auto zp UWORD")
|
||||
} else {
|
||||
out("$counterVar .word 0")
|
||||
}
|
||||
|
||||
out(endLabel)
|
||||
}
|
||||
|
||||
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
// note: A must have been loaded with the number of iterations!
|
||||
if(constIterations==0)
|
||||
return
|
||||
// note: A must have been loaded with the number of iterations already!
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
if(constIterations==null)
|
||||
out(" beq $endLabel")
|
||||
out(" beq $endLabel ; skip loop if zero iters")
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
out(" sta $counterVar")
|
||||
out(repeatLabel)
|
||||
translate(body)
|
||||
out("""
|
||||
dec $counterVar
|
||||
bne $repeatLabel
|
||||
beq $endLabel
|
||||
$counterVar .byte 0""")
|
||||
out(endLabel)
|
||||
}
|
||||
beq $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)
|
||||
}
|
||||
if(constIterations!=null && constIterations>=16 && zeropage.available() > 0) {
|
||||
// allocate count var on ZP TODO can be shared with countervars from other subroutines
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, body.position, errors)
|
||||
out("$counterVar = $zpAddr ; auto zp UBYTE")
|
||||
} else {
|
||||
out("$counterVar .byte 0")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -1095,7 +1093,7 @@ $counterVar .word 0""")
|
||||
out(whileLabel)
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
|
||||
translate(stmt.body)
|
||||
out(" jmp $whileLabel")
|
||||
jmp(whileLabel)
|
||||
out(endLabel)
|
||||
loopEndLabels.pop()
|
||||
}
|
||||
@ -1129,7 +1127,7 @@ $counterVar .word 0""")
|
||||
if(choice.values==null) {
|
||||
// the else choice
|
||||
translate(choice.statements)
|
||||
out(" jmp $endLabel")
|
||||
jmp(endLabel)
|
||||
} else {
|
||||
choiceBlocks.add(choiceLabel to choice.statements)
|
||||
for (cv in choice.values!!) {
|
||||
@ -1148,11 +1146,11 @@ $counterVar .word 0""")
|
||||
}
|
||||
}
|
||||
}
|
||||
out(" jmp $endLabel")
|
||||
jmp(endLabel)
|
||||
for(choiceBlock in choiceBlocks) {
|
||||
out(choiceBlock.first)
|
||||
translate(choiceBlock.second)
|
||||
out(" jmp $endLabel")
|
||||
jmp(endLabel)
|
||||
}
|
||||
out(endLabel)
|
||||
}
|
||||
@ -1211,7 +1209,7 @@ $counterVar .word 0""")
|
||||
val endLabel = makeLabel("branch_end")
|
||||
out(" $instruction $elseLabel")
|
||||
translate(stmt.truepart)
|
||||
out(" jmp $endLabel")
|
||||
jmp(endLabel)
|
||||
out(elseLabel)
|
||||
translate(stmt.elsepart)
|
||||
out(endLabel)
|
||||
@ -1254,7 +1252,9 @@ $counterVar .word 0""")
|
||||
"%asmbinary" -> {
|
||||
val offset = if(stmt.args.size>1) ", ${stmt.args[1].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" -> {
|
||||
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
|
||||
@ -1266,14 +1266,12 @@ $label nop""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(jmp: Jump) {
|
||||
out(" jmp ${getJumpTarget(jmp)}")
|
||||
}
|
||||
private fun translate(jump: Jump) = jmp(getJumpTarget(jump))
|
||||
|
||||
private fun getJumpTarget(jmp: Jump): String {
|
||||
val ident = jmp.identifier
|
||||
val label = jmp.generatedLabel
|
||||
val addr = jmp.address
|
||||
private fun getJumpTarget(jump: Jump): String {
|
||||
val ident = jump.identifier
|
||||
val label = jump.generatedLabel
|
||||
val addr = jump.address
|
||||
return when {
|
||||
ident!=null -> {
|
||||
val target = ident.targetStatement(program)
|
||||
@ -1289,7 +1287,7 @@ $label nop""")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(ret: Return) {
|
||||
internal fun translate(ret: Return, withRts: Boolean=true) {
|
||||
ret.value?.let { returnvalue ->
|
||||
val sub = ret.definingSubroutine()!!
|
||||
val returnType = sub.returntypes.single()
|
||||
@ -1308,7 +1306,9 @@ $label nop""")
|
||||
}
|
||||
}
|
||||
}
|
||||
out(" rts")
|
||||
|
||||
if(withRts)
|
||||
out(" rts")
|
||||
}
|
||||
|
||||
private fun translate(asm: InlineAssembly) {
|
||||
@ -1329,7 +1329,7 @@ $label nop""")
|
||||
// sign extend signed byte on stack to signed word on stack
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
if(compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(isTargetCpu(CpuType.CPU65c02))
|
||||
out(" stz P8ESTACK_HI+1,x")
|
||||
else
|
||||
out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||
@ -1343,7 +1343,7 @@ $label nop""")
|
||||
// sign extend signed byte in a var to a full word in that variable
|
||||
when(valueDt) {
|
||||
DataType.UBYTE -> {
|
||||
if(compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(isTargetCpu(CpuType.CPU65c02))
|
||||
out(" stz $asmvar+1")
|
||||
else
|
||||
out(" lda #0 | sta $asmvar+1")
|
||||
@ -1367,6 +1367,13 @@ $label nop""")
|
||||
return vardecl.makeScopedName(vardecl.name) in allocatedZeropageVariables
|
||||
}
|
||||
|
||||
internal fun jmp(asmLabel: String) {
|
||||
if(isTargetCpu(CpuType.CPU65c02))
|
||||
out(" bra $asmLabel") // note: 64tass will convert this automatically to a jmp if the relative distance is too large
|
||||
else
|
||||
out(" jmp $asmLabel")
|
||||
}
|
||||
|
||||
internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: Expression): Pair<Expression, Expression>? {
|
||||
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {
|
||||
val leftDt = pointerOffsetExpr.left.inferType(program)
|
||||
@ -1396,39 +1403,44 @@ $label nop""")
|
||||
|
||||
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) {
|
||||
when(ptrAndIndex.second) {
|
||||
is NumericLiteralValue, is IdentifierReference -> {
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" sta (${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(" sta (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// same as above but we need to save the A register
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
out(" pha")
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" pla")
|
||||
out(" sta (${asmSymbolName(pointervar)}),y")
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
out(" pha")
|
||||
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
|
||||
out(" pla")
|
||||
out(" sta (P8ZP_SCRATCH_W2),y")
|
||||
}
|
||||
}
|
||||
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)) {
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
|
||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
@ -1,16 +1,19 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
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.functions.FSignature
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.c64.codegen.assignment.*
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.*
|
||||
import prog8.compiler.target.subroutineFloatEvalResultVar2
|
||||
|
||||
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
|
||||
@ -48,7 +51,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
"ln", "log2", "sqrt", "rad",
|
||||
"deg", "round", "floor", "ceil",
|
||||
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
|
||||
"fastrnd8", "rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
|
||||
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"rol" -> funcRol(fcall)
|
||||
"rol2" -> funcRol2(fcall)
|
||||
@ -61,14 +64,83 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
"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}")
|
||||
"cmp" -> funcCmp(fcall)
|
||||
else -> throw AssemblyError("missing asmgen for builtin func ${func.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCmp(fcall: IFunctionCall) {
|
||||
val arg1 = fcall.args[0]
|
||||
val arg2 = fcall.args[1]
|
||||
val dt1 = arg1.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val dt2 = arg2.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes) {
|
||||
when (arg2) {
|
||||
is IdentifierReference -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp #${arg2.number}")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if(arg2.addressExpression is NumericLiteralValue) {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine())
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine())
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
} else
|
||||
throw AssemblyError("args for cmp() should have same dt")
|
||||
} else {
|
||||
// dt1 is a word
|
||||
if(dt2 in WordDatatypes) {
|
||||
when (arg2) {
|
||||
is IdentifierReference -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy ${asmgen.asmVariableName(arg2)}+1
|
||||
bne +
|
||||
cmp ${asmgen.asmVariableName(arg2)}
|
||||
+""")
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy #>${arg2.number}
|
||||
bne +
|
||||
cmp #<${arg2.number}
|
||||
+""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine())
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
cmp P8ZP_SCRATCH_W1
|
||||
+""")
|
||||
}
|
||||
}
|
||||
} else
|
||||
throw AssemblyError("args for cmp() should have same dt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if(discardResult || fcall !is FunctionCall)
|
||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||
val scope = fcall.definingScope()
|
||||
val nameRef = fcall.args[0] as IdentifierReference
|
||||
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
|
||||
val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
|
||||
@ -85,12 +157,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
|
||||
else
|
||||
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)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@ -396,8 +464,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
private fun translateRolRorArrayArgs(arrayvar: IdentifierReference, indexer: ArrayIndex, operation: String, dt: Char) {
|
||||
asmgen.assignExpressionToVariable(AddressOf(arrayvar, arrayvar.position), "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
|
||||
val indexerExpr = if(indexer.indexVar!=null) indexer.indexVar!! else indexer.indexNum!!
|
||||
asmgen.assignExpressionToVariable(indexerExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
|
||||
asmgen.assignExpressionToVariable(indexer.indexExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
|
||||
}
|
||||
|
||||
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
@ -480,11 +547,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
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)
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, 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)
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
asmgen.out(" jsr floats.func_${function.name}_f_fac1")
|
||||
@ -511,19 +578,19 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.ARRAY_UB, DataType.STR -> {
|
||||
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), RegisterOrPair.AY)
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
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)
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, 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)
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, 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)
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
asmgen.out(" jsr floats.func_sum_f_fac1")
|
||||
@ -612,10 +679,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
throw AssemblyError("unknown dt")
|
||||
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
|
||||
|
||||
val firstNum = first.indexer.indexNum
|
||||
val firstVar = first.indexer.indexVar
|
||||
val secondNum = second.indexer.indexNum
|
||||
val secondVar = second.indexer.indexVar
|
||||
val firstNum = first.indexer.indexExpr as? NumericLiteralValue
|
||||
val firstVar = first.indexer.indexExpr as? IdentifierReference
|
||||
val secondNum = second.indexer.indexExpr as? NumericLiteralValue
|
||||
val secondVar = second.indexer.indexExpr as? IdentifierReference
|
||||
|
||||
if(firstNum!=null && secondNum!=null) {
|
||||
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondNum)
|
||||
@ -651,12 +718,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val assignFirst = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W2"),
|
||||
targetFromExpr(first, datatype),
|
||||
false, first.position
|
||||
false, program.memsizer, first.position
|
||||
)
|
||||
val assignSecond = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W1"),
|
||||
targetFromExpr(second, datatype),
|
||||
false, second.position
|
||||
false, program.memsizer, second.position
|
||||
)
|
||||
asmgen.translateNormalAssignment(assignFirst)
|
||||
asmgen.translateNormalAssignment(assignSecond)
|
||||
@ -668,12 +735,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val assignFirst = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
|
||||
targetFromExpr(first, datatype),
|
||||
false, first.position
|
||||
false, program.memsizer, first.position
|
||||
)
|
||||
val assignSecond = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
|
||||
targetFromExpr(second, datatype),
|
||||
false, second.position
|
||||
false, program.memsizer, second.position
|
||||
)
|
||||
asmgen.translateNormalAssignment(assignFirst)
|
||||
asmgen.translateNormalAssignment(assignSecond)
|
||||
@ -683,8 +750,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
|
||||
val index1 = indexValue1.number.toInt() * asmgen.compTarget.memorySize(elementDt)
|
||||
val index2 = indexValue2.number.toInt() * asmgen.compTarget.memorySize(elementDt)
|
||||
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
|
||||
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out("""
|
||||
@ -797,7 +864,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) {
|
||||
val index1 = indexValue1.number.toInt() * asmgen.compTarget.memorySize(elementDt)
|
||||
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
|
||||
val idxAsmName2 = asmgen.asmVariableName(indexName2)
|
||||
when(elementDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
@ -856,7 +923,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) {
|
||||
val idxAsmName1 = asmgen.asmVariableName(indexName1)
|
||||
val index2 = indexValue2.number.toInt() * asmgen.compTarget.memorySize(elementDt)
|
||||
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out("""
|
||||
@ -926,15 +993,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
when (dt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" jsr prog8_lib.abs_b_into_A")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister!!, scope, program, asmgen), CpuRegister.A)
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, 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)
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" jsr floats.abs_f_fac1")
|
||||
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister!!, scope, program, asmgen), RegisterOrPair.FAC1)
|
||||
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
@ -943,6 +1010,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
when(func.name) {
|
||||
"fastrnd8" -> {
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_fastrnd8_stack")
|
||||
else {
|
||||
asmgen.out(" jsr math.fast_randbyte")
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
|
||||
}
|
||||
}
|
||||
"rnd" -> {
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_rnd_stack")
|
||||
@ -977,7 +1052,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
// 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.compTarget.machine.cpu == CpuType.CPU65c02) {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
asmgen.out("""
|
||||
sta ($varname)
|
||||
txa
|
||||
@ -1028,7 +1103,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val varname = asmgen.asmVariableName(addrExpr)
|
||||
if(asmgen.isZpVar(addrExpr)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02) {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
asmgen.out("""
|
||||
ldy #1
|
||||
lda ($varname),y
|
||||
@ -1292,7 +1367,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
conv.reg != null -> {
|
||||
@ -1308,7 +1383,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
else -> throw AssemblyError("callconv")
|
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ArrayElementTypes
|
||||
@ -57,9 +57,9 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
lda $varname
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
beq $endLabel
|
||||
$incdec $varname
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
$incdec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
|
||||
} else {
|
||||
|
||||
@ -117,16 +117,15 @@ $modifiedLabel2 cmp #0 ; modified
|
||||
asmgen.out("""
|
||||
+ inc $varname
|
||||
bne $loopLabel
|
||||
inc $varname+1
|
||||
jmp $loopLabel
|
||||
""")
|
||||
inc $varname+1""")
|
||||
asmgen.jmp(loopLabel)
|
||||
} else {
|
||||
asmgen.out("""
|
||||
+ lda $varname
|
||||
bne +
|
||||
dec $varname+1
|
||||
+ dec $varname
|
||||
jmp $loopLabel""")
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
@ -386,23 +385,25 @@ $loopLabel""")
|
||||
}
|
||||
-2 -> {
|
||||
when (range.last) {
|
||||
0 -> asmgen.out("""
|
||||
lda $varname
|
||||
beq $endLabel
|
||||
dec $varname
|
||||
dec $varname
|
||||
jmp $loopLabel""")
|
||||
0 -> {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
beq $endLabel
|
||||
dec $varname
|
||||
dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
}
|
||||
1 -> asmgen.out("""
|
||||
dec $varname
|
||||
beq $endLabel
|
||||
dec $varname
|
||||
bne $loopLabel""")
|
||||
dec $varname
|
||||
beq $endLabel
|
||||
dec $varname
|
||||
bne $loopLabel""")
|
||||
else -> asmgen.out("""
|
||||
dec $varname
|
||||
dec $varname
|
||||
lda $varname
|
||||
cmp #${range.last-2}
|
||||
bne $loopLabel""")
|
||||
dec $varname
|
||||
dec $varname
|
||||
lda $varname
|
||||
cmp #${range.last-2}
|
||||
bne $loopLabel""")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -413,8 +414,8 @@ $loopLabel""")
|
||||
beq $endLabel
|
||||
clc
|
||||
adc #${range.step}
|
||||
sta $varname
|
||||
jmp $loopLabel""")
|
||||
sta $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
}
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
@ -450,9 +451,9 @@ $loopLabel""")
|
||||
sta $varname
|
||||
lda $varname+1
|
||||
adc #>${range.step}
|
||||
sta $varname+1
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
sta $varname+1""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -502,9 +503,9 @@ $loopLabel""")
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
beq $endLabel
|
||||
dec $varname
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
1 -> {
|
||||
asmgen.out("""
|
||||
@ -545,9 +546,9 @@ $loopLabel""")
|
||||
beq $endLabel
|
||||
+ inc $varname
|
||||
bne $loopLabel
|
||||
inc $varname+1
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
inc $varname+1""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
@ -573,9 +574,9 @@ $loopLabel""")
|
||||
+ lda $varname
|
||||
bne +
|
||||
dec $varname+1
|
||||
+ dec $varname
|
||||
jmp $loopLabel
|
||||
$endLabel""")
|
||||
+ dec $varname""")
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
@ -8,10 +8,10 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CpuType
|
||||
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
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
|
||||
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@ -91,11 +91,15 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
when {
|
||||
stmt.args.all {isNoClobberRisk(it)} -> {
|
||||
// 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 (cx16virtualRegsArgsInfo, otherRegsArgsInfo) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
|
||||
for(arg in cx16virtualRegsArgsInfo)
|
||||
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
|
||||
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
|
||||
for(arg in cx16virtualRegs)
|
||||
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)
|
||||
}
|
||||
else -> {
|
||||
@ -107,17 +111,24 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
|
||||
if(sub.inline && asmgen.options.optimize) {
|
||||
if(sub.containsDefinedVariables())
|
||||
throw AssemblyError("can't inline sub with vars")
|
||||
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
|
||||
throw AssemblyError("can't inline a non-asm subroutine with parameters")
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name} from ${sub.position}")
|
||||
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
|
||||
statements.forEach { asmgen.translate(it) }
|
||||
}
|
||||
else {
|
||||
if(!sub.inline || !asmgen.options.optimize) {
|
||||
asmgen.out(" jsr $subName")
|
||||
} else {
|
||||
// inline the subroutine.
|
||||
// we do this by copying the subroutine's statements at the call site.
|
||||
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||
// (this condition has been enforced by an ast check earlier)
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
|
||||
statements.forEach {
|
||||
if(it is Return) {
|
||||
asmgen.translate(it, false) // don't use RTS for the inlined return statement
|
||||
} else {
|
||||
if(!sub.inline || it !is VarDecl)
|
||||
asmgen.translate(it)
|
||||
}
|
||||
}
|
||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||
}
|
||||
|
||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||
@ -168,12 +179,12 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
lda P8ESTACK_LO$plusIdxStr,x
|
||||
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
|
||||
""")
|
||||
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
|
||||
}
|
||||
in WordDatatypes ->
|
||||
in WordDatatypes, in IterableDatatypes ->
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO$plusIdxStr,x
|
||||
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
|
||||
@ -318,7 +329,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
} else {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
package prog8.compiler.target.cpu6502.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@ -66,8 +66,9 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
targetArrayIdx!=null -> {
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
||||
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(targetArrayIdx.indexer.indexNum!=null) {
|
||||
val indexValue = targetArrayIdx.indexer.constIndex()!! * asmgen.compTarget.memorySize(elementDt)
|
||||
val constIndex = targetArrayIdx.indexer.constIndex()
|
||||
if(constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||
in WordDatatypes -> {
|
@ -1,12 +1,12 @@
|
||||
package prog8.compiler.target.c64.codegen.assignment
|
||||
package prog8.compiler.target.cpu6502.codegen.assignment
|
||||
|
||||
import prog8.ast.IMemSizer
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
|
||||
|
||||
internal enum class TargetStorageKind {
|
||||
@ -120,13 +120,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
asmgen.asmVariableName(array.arrayvar)
|
||||
|
||||
companion object {
|
||||
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||
return when {
|
||||
indexer.indexNum!=null -> fromAstSource(indexer.indexNum!!, program, asmgen)
|
||||
indexer.indexVar!=null -> fromAstSource(indexer.indexVar!!, program, asmgen)
|
||||
else -> throw AssemblyError("weird indexer")
|
||||
}
|
||||
}
|
||||
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource = fromAstSource(indexer.indexExpr, program, asmgen)
|
||||
|
||||
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||
val cv = value.constValue(program)
|
||||
@ -207,12 +201,13 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
internal class AsmAssignment(val source: AsmAssignSource,
|
||||
val target: AsmAssignTarget,
|
||||
val isAugmentable: Boolean,
|
||||
memsizer: IMemSizer,
|
||||
val position: Position) {
|
||||
|
||||
init {
|
||||
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
|
||||
require(ICompilationTarget.instance.memorySize(source.datatype) <= ICompilationTarget.instance.memorySize(target.datatype)) {
|
||||
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||
"source storage size must be less or equal to target datatype storage size"
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64.codegen.assignment
|
||||
package prog8.compiler.target.cpu6502.codegen.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@ -9,12 +9,13 @@ import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.functions.BuiltinFunctions
|
||||
import prog8.compiler.functions.builtinFunctionReturnType
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen,
|
||||
private val exprAsmgen: ExpressionsAsmGen) {
|
||||
private val exprAsmgen: ExpressionsAsmGen
|
||||
) {
|
||||
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen)
|
||||
|
||||
@ -22,7 +23,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
|
||||
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
|
||||
|
||||
if(assign.isAugmentable)
|
||||
@ -64,9 +65,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val value = assign.source.array!!
|
||||
val elementDt = assign.source.datatype
|
||||
val arrayVarName = asmgen.asmVariableName(value.arrayvar)
|
||||
if (value.indexer.indexNum!=null) {
|
||||
val constIndex = value.indexer.constIndex()
|
||||
if (constIndex!=null) {
|
||||
// constant array index value
|
||||
val indexValue = value.indexer.constIndex()!! * asmgen.compTarget.memorySize(elementDt)
|
||||
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
||||
when (elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $arrayVarName+$indexValue")
|
||||
@ -114,7 +116,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
SourceStorageKind.MEMORY -> {
|
||||
fun assignViaExprEval(expression: Expression) {
|
||||
assignExpressionToVariable(expression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope)
|
||||
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
|
||||
@ -319,7 +321,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
|
||||
fun assignViaExprEval(addressExpression: Expression) {
|
||||
asmgen.assignExpressionToVariable(addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
|
||||
@ -356,7 +358,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
|
||||
|
||||
// special case optimizations
|
||||
if(target.kind==TargetStorageKind.VARIABLE) {
|
||||
if(target.kind== TargetStorageKind.VARIABLE) {
|
||||
if(value is IdentifierReference && valueDt != DataType.STRUCT)
|
||||
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
|
||||
|
||||
@ -429,8 +431,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
|
||||
// give up, do it via eval stack
|
||||
// TODO optimize typecasts for more special cases?
|
||||
// note: cannot use assignTypeCastedValue because that is ourselves :P
|
||||
// TODO optimize typecasts for more special cases?
|
||||
if(this.asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used for typecast: $value into $targetDt (target=${target.kind} at ${value.position}")
|
||||
asmgen.translateExpression(origTypeCastExpression) // this performs the actual type cast in translateExpression(Typecast)
|
||||
@ -441,7 +443,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
|
||||
lsb.linkParents(value.parent)
|
||||
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)
|
||||
}
|
||||
|
||||
@ -473,7 +475,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if(asmgen.compTarget.machine.cpu==CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
@ -496,7 +498,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(asmgen.compTarget.machine.cpu==CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
@ -569,11 +571,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.STR -> {
|
||||
if (targetDt != DataType.UWORD && targetDt == DataType.STR)
|
||||
throw AssemblyError("cannot typecast a string into another incompatitble type")
|
||||
TODO("assign typecasted string into target var")
|
||||
}
|
||||
DataType.STR -> throw AssemblyError("cannot typecast a string value")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
@ -592,7 +590,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if(asmgen.compTarget.machine.cpu==CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
@ -620,7 +618,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(asmgen.compTarget.machine.cpu==CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
|
||||
else
|
||||
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
|
||||
@ -708,11 +706,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.STR -> {
|
||||
if (targetDt != DataType.UWORD && targetDt == DataType.STR)
|
||||
throw AssemblyError("cannot typecast a string into another incompatitble type")
|
||||
TODO("assign typecasted string into target var")
|
||||
}
|
||||
DataType.STR -> throw AssemblyError("cannot typecast a string value")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
@ -762,7 +756,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if(target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * asmgen.compTarget.memorySize(target.datatype)
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
|
||||
@ -968,7 +962,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
TargetStorageKind.ARRAY -> {
|
||||
target.array!!
|
||||
if(target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * asmgen.compTarget.memorySize(target.datatype)
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
@ -1073,11 +1067,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
ldy #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1""")
|
||||
if(target.array!!.indexer.indexNum!=null) {
|
||||
val index = target.array.indexer.constIndex()!!
|
||||
asmgen.out(" lda #$index")
|
||||
val constIndex = target.array!!.indexer.constIndex()
|
||||
if(constIndex!=null) {
|
||||
asmgen.out(" lda #$constIndex")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||
asmgen.out(" lda $asmvarname")
|
||||
}
|
||||
asmgen.out(" jsr floats.set_array_float_from_fac1")
|
||||
@ -1109,11 +1103,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
ldy #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1""")
|
||||
if(target.array!!.indexer.indexNum!=null) {
|
||||
val index = target.array.indexer.constIndex()!!
|
||||
asmgen.out(" lda #$index")
|
||||
val constIndex = target.array!!.indexer.constIndex()
|
||||
if(constIndex!=null) {
|
||||
asmgen.out(" lda #$constIndex")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||
asmgen.out(" lda $asmvarname")
|
||||
}
|
||||
asmgen.out(" jsr floats.set_array_float")
|
||||
@ -1156,11 +1150,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
ldy #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1""")
|
||||
if(target.array!!.indexer.indexNum!=null) {
|
||||
val index = target.array.indexer.constIndex()!!
|
||||
asmgen.out(" lda #$index")
|
||||
val constIndex = target.array!!.indexer.constIndex()
|
||||
if(constIndex!=null) {
|
||||
asmgen.out(" lda #$constIndex")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||
asmgen.out(" lda $asmvarname")
|
||||
}
|
||||
asmgen.out(" jsr floats.set_array_float")
|
||||
@ -1191,7 +1185,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if (target.constArrayIndexValue!=null) {
|
||||
val scaledIdx = target.constArrayIndexValue!! * asmgen.compTarget.memorySize(target.datatype)
|
||||
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
else {
|
||||
@ -1242,11 +1236,20 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// TODO optimize slow stack evaluation for this case, see assignVariableUByteIntoWord
|
||||
if(this.asmgen.options.slowCodegenWarnings)
|
||||
println("warning: slow stack evaluation used for sign-extend byte typecast at ${bytevar.position}")
|
||||
asmgen.translateExpression(wordtarget.origAssign.source.expression!!)
|
||||
assignStackValue(wordtarget)
|
||||
if (wordtarget.constArrayIndexValue!=null) {
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
||||
asmgen.out(" lda $sourceName")
|
||||
asmgen.signExtendAYlsb(DataType.BYTE)
|
||||
asmgen.out(" sta ${wordtarget.asmVarname}+$scaledIdx | sty ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||
}
|
||||
else {
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, wordtarget.scope!!)
|
||||
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array!!, wordtarget.datatype, CpuRegister.X)
|
||||
asmgen.out(" lda $sourceName")
|
||||
asmgen.signExtendAYlsb(DataType.BYTE)
|
||||
asmgen.out(" sta ${wordtarget.asmVarname},x | inx | tya | sta ${wordtarget.asmVarname},x")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(wordtarget.register!!) {
|
||||
@ -1295,7 +1298,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
when(wordtarget.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz ${wordtarget.asmVarname}+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
|
||||
@ -1304,7 +1307,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
if (wordtarget.constArrayIndexValue!=null) {
|
||||
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
|
||||
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1")
|
||||
@ -1329,7 +1332,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ESTACK_HI,x | dex")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
|
||||
@ -1339,8 +1342,16 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
|
||||
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
||||
if(target.register !in Cx16VirtualRegisters)
|
||||
require(target.datatype in ByteDatatypes)
|
||||
// we make an exception in the type check for assigning something to a cx16 virtual register
|
||||
if(target.register !in Cx16VirtualRegisters) {
|
||||
if(target.kind==TargetStorageKind.VARIABLE) {
|
||||
val parts = target.asmVarname.split('.')
|
||||
if (parts.size != 2 || parts[0] != "cx16")
|
||||
require(target.datatype in ByteDatatypes)
|
||||
} else {
|
||||
require(target.datatype in ByteDatatypes)
|
||||
}
|
||||
}
|
||||
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -1368,7 +1379,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
CpuRegister.X -> asmgen.out(" txa")
|
||||
CpuRegister.Y -> asmgen.out(" tya")
|
||||
}
|
||||
asmgen.out(" ldy ${asmgen.asmVariableName(target.array!!.indexer.indexVar!!)} | sta ${target.asmVarname},y")
|
||||
val indexVar = target.array!!.indexer.indexExpr as IdentifierReference
|
||||
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta ${target.asmVarname},y")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
@ -1571,7 +1583,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
|
||||
private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
|
||||
if(word==0 && asmgen.compTarget.machine.cpu == CpuType.CPU65c02) {
|
||||
if(word==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
// optimize setting zero value for this processor
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -1666,7 +1678,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
|
||||
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
|
||||
if(byte==0.toShort() && asmgen.compTarget.machine.cpu == CpuType.CPU65c02) {
|
||||
if(byte==0.toShort() && asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
// optimize setting zero value for this cpu
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -1736,7 +1748,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
|
||||
in Cx16VirtualRegisters -> {
|
||||
asmgen.out(" lda #${byte.toHex()} | sta cx16.${target.register.toString().toLowerCase()}")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()}+1\n")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta cx16.${target.register.toString().toLowerCase()}+1\n")
|
||||
@ -1757,7 +1769,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
// optimized case for float zero
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out("""
|
||||
stz ${target.asmVarname}
|
||||
stz ${target.asmVarname}+1
|
||||
@ -1776,9 +1788,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
if (target.array!!.indexer.indexNum!=null) {
|
||||
val indexValue = target.array.indexer.constIndex()!! * asmgen.compTarget.memorySize(DataType.FLOAT)
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
val constIndex = target.array!!.indexer.constIndex()
|
||||
if (constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out("""
|
||||
stz ${target.asmVarname}+$indexValue
|
||||
stz ${target.asmVarname}+$indexValue+1
|
||||
@ -1796,7 +1809,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sta ${target.asmVarname}+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
@ -1841,8 +1854,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val arrayVarName = target.asmVarname
|
||||
if (target.array!!.indexer.indexNum!=null) {
|
||||
val indexValue = target.array.indexer.constIndex()!! * asmgen.compTarget.memorySize(DataType.FLOAT)
|
||||
val constIndex = target.array!!.indexer.constIndex()
|
||||
if (constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta $arrayVarName+$indexValue
|
||||
@ -1856,7 +1870,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sta $arrayVarName+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
|
||||
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
|
||||
asmgen.out("""
|
||||
lda #<${constFloat}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
@ -1975,7 +1989,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
when(wordtarget.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out(" lda ${address.toHex()} | sta ${wordtarget.asmVarname}")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz ${wordtarget.asmVarname}+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
|
||||
@ -1991,7 +2005,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.out(" lda ${address.toHex()} | sta P8ESTACK_LO,x")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ESTACK_HI,x | dex")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
|
||||
@ -2003,7 +2017,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.loadByteFromPointerIntoA(identifier)
|
||||
asmgen.out(" sta ${wordtarget.asmVarname}")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz ${wordtarget.asmVarname}+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
|
||||
@ -2023,7 +2037,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.loadByteFromPointerIntoA(identifier)
|
||||
asmgen.out(" sta P8ESTACK_LO,x")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ESTACK_HI,x | dex")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
|
||||
@ -2041,7 +2055,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
when(addressExpr) {
|
||||
is NumericLiteralValue, is IdentifierReference -> {
|
||||
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
|
||||
@ -2051,7 +2065,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out(" pha")
|
||||
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
|
||||
asmgen.out(" pla")
|
||||
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
|
||||
else
|
||||
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
|
||||
@ -2063,7 +2077,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
val sourceName = asmgen.asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
if (asmgen.compTarget.machine.cpu == CpuType.CPU65c02) {
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
if (asmgen.isZpVar(scopedName)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.out(" sta ($sourceName)")
|
||||
@ -2109,21 +2123,21 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) {
|
||||
val src = AsmAssignSource.fromAstSource(expr, 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)
|
||||
}
|
||||
|
||||
internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) {
|
||||
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
|
||||
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)
|
||||
}
|
||||
|
||||
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) {
|
||||
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
|
||||
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)
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package prog8.compiler.target.c64.codegen.assignment
|
||||
package prog8.compiler.target.cpu6502.codegen.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
@ -7,14 +7,14 @@ import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.AsmGen
|
||||
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
|
||||
|
||||
internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private val assignmentAsmGen: AssignmentAsmGen,
|
||||
private val exprAsmGen: ExpressionsAsmGen,
|
||||
private val asmgen: AsmGen) {
|
||||
private val asmgen: AsmGen
|
||||
) {
|
||||
fun translate(assign: AsmAssignment) {
|
||||
require(assign.isAugmentable)
|
||||
require(assign.source.kind== SourceStorageKind.EXPRESSION)
|
||||
@ -160,7 +160,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable(addr.toHex(), DataType.UBYTE, operator, ident)
|
||||
// TODO more specialized code for types such as memory read etc. -> inplaceModification_byte_memread_to_variable()
|
||||
memread != null -> inplaceModification_byte_memread_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
||||
@ -199,9 +199,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
with(target.array!!.indexer) {
|
||||
val indexNum = indexExpr as? NumericLiteralValue
|
||||
val indexVar = indexExpr as? IdentifierReference
|
||||
when {
|
||||
indexNum!=null -> {
|
||||
val targetVarName = "${target.asmVarname} + ${indexNum!!.number.toInt()*asmgen.compTarget.memorySize(target.datatype)}"
|
||||
val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
when {
|
||||
@ -245,19 +247,19 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
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.assignRegisterByte(target, CpuRegister.A)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
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.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
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.assignFAC1float(target)
|
||||
}
|
||||
@ -268,8 +270,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> TODO("reg in-place modification")
|
||||
TargetStorageKind.STACK -> TODO("stack in-place modification")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg in-place modification")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack in-place modification")
|
||||
}
|
||||
}
|
||||
|
||||
@ -320,7 +322,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
|
||||
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
|
||||
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
if(ptrOnZp)
|
||||
@ -361,7 +362,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"&", "and" -> asmgen.out(" and $otherName")
|
||||
"|", "or" -> asmgen.out(" ora $otherName")
|
||||
"^", "xor" -> asmgen.out(" eor $otherName")
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
if(ptrOnZp)
|
||||
@ -464,7 +464,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else
|
||||
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
|
||||
}
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -540,7 +539,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -598,7 +596,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name")
|
||||
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name")
|
||||
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name")
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -631,7 +628,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
"<<" -> {
|
||||
if(value>=8) {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name")
|
||||
@ -642,7 +639,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
if(value>0) {
|
||||
if (dt == DataType.UBYTE) {
|
||||
if(value>=8) {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name")
|
||||
@ -670,7 +667,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"&", "and" -> asmgen.out(" lda $name | and #$value | sta $name")
|
||||
"|", "or" -> asmgen.out(" lda $name | ora #$value | sta $name")
|
||||
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -692,8 +688,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
sta $name""")
|
||||
// TODO: tuned code for more operators
|
||||
}
|
||||
// TODO: tuned code for more operators
|
||||
else -> {
|
||||
inplaceModification_byte_value_to_variable(name, dt, operator, memread)
|
||||
}
|
||||
@ -723,8 +719,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
bcc +
|
||||
dec $name+1
|
||||
+""")
|
||||
// TODO: tuned code for more operators
|
||||
}
|
||||
// TODO: tuned code for more operators
|
||||
else -> {
|
||||
inplaceModification_word_value_to_variable(name, dt, operator, memread)
|
||||
}
|
||||
@ -857,14 +853,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"<<" -> {
|
||||
when {
|
||||
value>=16 -> {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name | stz $name+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name | sta $name+1")
|
||||
}
|
||||
value==8 -> {
|
||||
asmgen.out(" lda $name | sta $name+1")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name")
|
||||
@ -884,14 +880,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
if(dt==DataType.UWORD) {
|
||||
when {
|
||||
value>=16 -> {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name | stz $name+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name | sta $name+1")
|
||||
}
|
||||
value==8 -> {
|
||||
asmgen.out(" lda $name+1 | sta $name")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name+1")
|
||||
@ -940,13 +936,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"&", "and" -> {
|
||||
when {
|
||||
value == 0 -> {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name | stz $name+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name | sta $name+1")
|
||||
}
|
||||
value and 255 == 0 -> {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name")
|
||||
@ -954,7 +950,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
value < 0x0100 -> {
|
||||
asmgen.out(" lda $name | and #$value | sta $name")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name+1")
|
||||
@ -978,7 +974,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else -> asmgen.out(" lda $name | eor #<$value | sta $name | lda $name+1 | eor #>$value | sta $name+1")
|
||||
}
|
||||
}
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -1041,7 +1036,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
"*" -> {
|
||||
asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1")
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ZP_SCRATCH_W1+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1")
|
||||
@ -1054,8 +1049,49 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1""")
|
||||
}
|
||||
"/" -> TODO("div (u)wordvar/bytevar")
|
||||
"%" -> TODO("(u)word remainder bytevar")
|
||||
"/" -> {
|
||||
if(dt==DataType.UWORD) {
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $otherName
|
||||
ldy #0
|
||||
jsr math.divmod_uw_asm
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $otherName
|
||||
ldy #0
|
||||
jsr math.divmod_w_asm
|
||||
sta $name
|
||||
sty $name+1
|
||||
""")
|
||||
}
|
||||
}
|
||||
"%" -> {
|
||||
if(valueDt!=DataType.UBYTE || dt!=DataType.UWORD)
|
||||
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
ldy $name+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda $otherName
|
||||
ldy #0
|
||||
jsr math.divmod_uw_asm
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta $name
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta $name+1
|
||||
""") }
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
@ -1092,7 +1128,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"&", "and" -> {
|
||||
asmgen.out(" lda $otherName | and $name | sta $name")
|
||||
if(dt in WordDatatypes) {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name+1")
|
||||
@ -1100,7 +1136,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
"|", "or" -> asmgen.out(" lda $otherName | ora $name | sta $name")
|
||||
"^", "xor" -> asmgen.out(" lda $otherName | eor $name | sta $name")
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -1174,7 +1209,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name | lda $name+1 | and $otherName+1 | sta $name+1")
|
||||
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name | lda $name+1 | ora $otherName+1 | sta $name+1")
|
||||
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name | lda $name+1 | eor $otherName+1 | sta $name+1")
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -1351,7 +1385,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out(" and $name | sta $name")
|
||||
if(dt in WordDatatypes) {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz $name+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta $name+1")
|
||||
@ -1365,7 +1399,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
asmgen.out(" eor $name | sta $name")
|
||||
}
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -1406,7 +1439,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
|
||||
asmgen.out(" eor $name | sta $name | tya | eor $name+1 | sta $name+1")
|
||||
}
|
||||
in comparisonOperators -> TODO("in-place modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -1454,7 +1486,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
jsr floats.FDIV
|
||||
""")
|
||||
}
|
||||
in comparisonOperators -> TODO("in-place float modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
||||
}
|
||||
asmgen.out("""
|
||||
@ -1474,7 +1505,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope)
|
||||
when (operator) {
|
||||
"**" -> {
|
||||
if(asmgen.compTarget 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()
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
@ -1485,15 +1525,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
jsr floats.MOVFM
|
||||
jsr floats.FPWRT
|
||||
""")
|
||||
} else
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr floats.CONUPK
|
||||
lda #<$otherName
|
||||
ldy #>$otherName
|
||||
jsr floats.FPWR
|
||||
""")
|
||||
}
|
||||
"+" -> {
|
||||
asmgen.out("""
|
||||
@ -1535,7 +1566,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
jsr floats.FDIV
|
||||
""")
|
||||
}
|
||||
in comparisonOperators -> TODO("in-place float modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
||||
}
|
||||
// store Fac1 back into memory
|
||||
@ -1552,7 +1582,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope)
|
||||
when (operator) {
|
||||
"**" -> {
|
||||
if(asmgen.compTarget 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()
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
@ -1563,15 +1602,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
jsr floats.MOVFM
|
||||
jsr floats.FPWRT
|
||||
""")
|
||||
} else
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr floats.CONUPK
|
||||
lda #<$constValueName
|
||||
ldy #>$constValueName
|
||||
jsr floats.FPWR
|
||||
""")
|
||||
}
|
||||
"+" -> {
|
||||
if (value == 0.0)
|
||||
@ -1620,7 +1650,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
jsr floats.FDIV
|
||||
""")
|
||||
}
|
||||
in comparisonOperators -> TODO("in-place float modification for $operator")
|
||||
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
||||
}
|
||||
// store Fac1 back into memory
|
||||
@ -1644,7 +1673,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz ${target.asmVarname}+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta ${target.asmVarname}+1")
|
||||
@ -1654,7 +1683,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
if(asmgen.compTarget.machine.cpu == CpuType.CPU65c02)
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ESTACK_HI+1,x")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||
@ -1730,9 +1759,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("in-place not of ubyte array")
|
||||
TargetStorageKind.REGISTER -> TODO("reg not")
|
||||
TargetStorageKind.STACK -> TODO("stack not")
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of ubyte array")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@ -1749,9 +1778,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta ${target.asmVarname}+1""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory not")
|
||||
TargetStorageKind.ARRAY -> TODO("in-place not of uword array")
|
||||
TargetStorageKind.REGISTER -> TODO("reg not")
|
||||
TargetStorageKind.STACK -> TODO("stack not")
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of uword array")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("boolean-not of invalid type")
|
||||
@ -1796,9 +1825,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("in-place invert ubyte array")
|
||||
TargetStorageKind.REGISTER -> TODO("reg invert")
|
||||
TargetStorageKind.STACK -> TODO("stack invert")
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert ubyte array")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
@ -1813,9 +1842,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta ${target.asmVarname}+1""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory invert")
|
||||
TargetStorageKind.ARRAY -> TODO("in-place invert uword array")
|
||||
TargetStorageKind.REGISTER -> TODO("reg invert")
|
||||
TargetStorageKind.STACK -> TODO("stack invert")
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert uword array")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invert of invalid type")
|
||||
@ -1834,9 +1863,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta ${target.asmVarname}""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't in-place negate memory ubyte")
|
||||
TargetStorageKind.ARRAY -> TODO("in-place negate byte array")
|
||||
TargetStorageKind.REGISTER -> TODO("reg negate")
|
||||
TargetStorageKind.STACK -> TODO("stack negate")
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate byte array")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
@ -1851,10 +1880,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sbc ${target.asmVarname}+1
|
||||
sta ${target.asmVarname}+1""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("in-place negate word array")
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate word array")
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for word memory negate")
|
||||
TargetStorageKind.REGISTER -> TODO("reg negate")
|
||||
TargetStorageKind.STACK -> TODO("stack negate")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
@ -1867,8 +1896,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("in-place negate float array")
|
||||
TargetStorageKind.STACK -> TODO("stack float negate")
|
||||
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate float array")
|
||||
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack float negate")
|
||||
else -> throw AssemblyError("weird target kind for float")
|
||||
}
|
||||
}
|
@ -1,12 +1,9 @@
|
||||
package prog8.compiler.target.cx16
|
||||
|
||||
import prog8.ast.IStringEncoding
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.IMachineDefinition
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.parser.ModuleImporter
|
||||
import java.io.IOException
|
||||
|
||||
internal object CX16MachineDefinition: IMachineDefinition {
|
||||
@ -28,16 +25,11 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
override lateinit var zeropage: Zeropage
|
||||
|
||||
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
|
||||
|
||||
override fun importLibs(
|
||||
compilerOptions: CompilationOptions,
|
||||
importer: ModuleImporter,
|
||||
program: Program,
|
||||
encoder: IStringEncoding,
|
||||
compilationTargetName: String)
|
||||
{
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
importer.importLibraryModule(program, "syslib", encoder, compilationTargetName)
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
listOf("syslib")
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
@ -10,8 +11,7 @@ import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
|
||||
internal class BinExprSplitter(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
|
||||
@ -57,13 +57,14 @@ X = BinExpr X = LeftExpr
|
||||
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
|
||||
return noModifications
|
||||
|
||||
if(isSimpleExpression(binExpr.right) && !assignment.isAugmentable) {
|
||||
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
|
||||
if(binExpr.right.isSimple && !assignment.isAugmentable) {
|
||||
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, firstAssign, assignment.definingScope()),
|
||||
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
|
||||
IAstModification.ReplaceNode(binExpr, augExpr, assignment),
|
||||
IAstModification.InsertBefore(assignment, firstAssign, assignment.parent as INameScope)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,12 +76,9 @@ X = BinExpr X = LeftExpr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
private fun isSimpleTarget(target: AssignTarget, program: Program) =
|
||||
if (target.identifier!=null || target.memoryAddress!=null)
|
||||
ICompilationTarget.instance.isInRegularRAM(target, program)
|
||||
compTarget.isInRegularRAM(target, program)
|
||||
else
|
||||
false
|
||||
|
||||
|
@ -1,139 +1,72 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.compiler.ErrorReporter
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.loadAsmIncludeFile
|
||||
|
||||
private val alwaysKeepSubroutines = setOf(
|
||||
Pair("main", "start"),
|
||||
Pair("irq", "irq")
|
||||
)
|
||||
|
||||
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)
|
||||
import prog8.compiler.IErrorReporter
|
||||
|
||||
|
||||
class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
|
||||
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
|
||||
val usedSymbols = mutableSetOf<Statement>()
|
||||
val imports = mutableMapOf<Module, Set<Module>>().withDefault { setOf() }
|
||||
val importedBy = mutableMapOf<Module, Set<Module>>().withDefault { setOf() }
|
||||
val calls = mutableMapOf<Subroutine, Set<Subroutine>>().withDefault { setOf() }
|
||||
val calledBy = mutableMapOf<Subroutine, Set<Node>>().withDefault { setOf() }
|
||||
private val allIdentifiersAndTargets = mutableMapOf<Pair<IdentifierReference, Position>, Statement>()
|
||||
private val allAssemblyNodes = mutableListOf<InlineAssembly>()
|
||||
|
||||
init {
|
||||
visit(program)
|
||||
}
|
||||
|
||||
fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
|
||||
fun findSubs(scope: INameScope) {
|
||||
scope.statements.forEach {
|
||||
if (it is Subroutine)
|
||||
sub(it)
|
||||
if (it is INameScope)
|
||||
findSubs(it)
|
||||
private val usedSubroutines: Set<Subroutine> by lazy {
|
||||
calledBy.keys + program.entrypoint()
|
||||
}
|
||||
|
||||
private val usedBlocks: Set<Block> by lazy {
|
||||
val blocksFromSubroutines = usedSubroutines.map { it.definingBlock() }
|
||||
val blocksFromLibraries = program.allBlocks().filter { it.isInLibrary }
|
||||
val used = mutableSetOf<Block>()
|
||||
|
||||
allIdentifiersAndTargets.forEach {
|
||||
if(it.key.first.definingBlock() in blocksFromSubroutines) {
|
||||
val target = it.value.definingBlock()
|
||||
used.add(target)
|
||||
}
|
||||
}
|
||||
findSubs(scope)
|
||||
|
||||
used + blocksFromLibraries + program.entrypoint().definingBlock()
|
||||
}
|
||||
|
||||
override fun visit(program: Program) {
|
||||
super.visit(program)
|
||||
|
||||
program.modules.forEach {
|
||||
it.importedBy.clear()
|
||||
it.imports.clear()
|
||||
|
||||
it.importedBy.addAll(importedBy.getValue(it))
|
||||
it.imports.addAll(imports.getValue(it))
|
||||
}
|
||||
|
||||
val rootmodule = program.modules.first()
|
||||
rootmodule.importedBy.add(rootmodule) // don't discard root module
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
if (block.definingModule().isLibraryModule) {
|
||||
// make sure the block is not removed
|
||||
addNodeAndParentScopes(block)
|
||||
}
|
||||
|
||||
super.visit(block)
|
||||
private val usedModules: Set<Module> by lazy {
|
||||
usedBlocks.map { it.definingModule() }.toSet()
|
||||
}
|
||||
|
||||
override fun visit(directive: Directive) {
|
||||
val thisModule = directive.definingModule()
|
||||
if (directive.directive == "%import") {
|
||||
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
||||
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
|
||||
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
|
||||
} else if (directive.directive == "%asminclude") {
|
||||
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
|
||||
val scope = directive.definingSubroutine()
|
||||
if(scope!=null) {
|
||||
scanAssemblyCode(asm, directive, scope)
|
||||
}
|
||||
imports[thisModule] = imports.getValue(thisModule) + importedModule
|
||||
importedBy[importedModule] = importedBy.getValue(importedModule) + thisModule
|
||||
}
|
||||
|
||||
super.visit(directive)
|
||||
}
|
||||
|
||||
override fun visit(identifier: IdentifierReference) {
|
||||
// track symbol usage
|
||||
val target = identifier.targetStatement(program)
|
||||
if (target != null) {
|
||||
addNodeAndParentScopes(target)
|
||||
}
|
||||
super.visit(identifier)
|
||||
}
|
||||
|
||||
private fun addNodeAndParentScopes(stmt: Statement) {
|
||||
usedSymbols.add(stmt)
|
||||
var node: Node = stmt
|
||||
do {
|
||||
if (node is INameScope && node is Statement) {
|
||||
usedSymbols.add(node)
|
||||
}
|
||||
node = node.parent
|
||||
} while (node !is Module && node !is ParentSentinel)
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|
||||
|| subroutine.definingModule().isLibraryModule) {
|
||||
// make sure the entrypoint is mentioned in the used symbols
|
||||
addNodeAndParentScopes(subroutine)
|
||||
}
|
||||
super.visit(subroutine)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
if (decl.autogeneratedDontRemove || decl.datatype==DataType.STRUCT)
|
||||
addNodeAndParentScopes(decl)
|
||||
else if(decl.parent is Block && decl.definingModule().isLibraryModule)
|
||||
addNodeAndParentScopes(decl)
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val otherSub = functionCall.target.targetSubroutine(program)
|
||||
if (otherSub != null) {
|
||||
functionCall.definingSubroutine()?.let { thisSub ->
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
|
||||
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub) + functionCall
|
||||
}
|
||||
}
|
||||
super.visit(functionCall)
|
||||
@ -143,77 +76,44 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val otherSub = functionCallStatement.target.targetSubroutine(program)
|
||||
if (otherSub != null) {
|
||||
functionCallStatement.definingSubroutine()?.let { thisSub ->
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
|
||||
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub) + 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) + otherSub
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub) + thisSub
|
||||
}
|
||||
}
|
||||
super.visit(addressOf)
|
||||
}
|
||||
|
||||
override fun visit(jump: Jump) {
|
||||
val otherSub = jump.identifier?.targetSubroutine(program)
|
||||
if (otherSub != null) {
|
||||
jump.definingSubroutine()?.let { thisSub ->
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
|
||||
calls[thisSub] = calls.getValue(thisSub) + otherSub
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub) + jump
|
||||
}
|
||||
}
|
||||
super.visit(jump)
|
||||
}
|
||||
|
||||
override fun visit(structDecl: StructDecl) {
|
||||
usedSymbols.add(structDecl)
|
||||
usedSymbols.addAll(structDecl.statements)
|
||||
override fun visit(identifier: IdentifierReference) {
|
||||
allIdentifiersAndTargets[Pair(identifier, identifier.position)] = identifier.targetStatement(program)!!
|
||||
}
|
||||
|
||||
override fun visit(inlineAssembly: InlineAssembly) {
|
||||
// parse inline asm for subroutine calls (jmp, jsr)
|
||||
val scope = inlineAssembly.definingSubroutine()
|
||||
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
|
||||
super.visit(inlineAssembly)
|
||||
allAssemblyNodes.add(inlineAssembly)
|
||||
}
|
||||
|
||||
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
|
||||
asm.lines().forEach { line ->
|
||||
val matches = asmJumpRx.matchEntire(line)
|
||||
if (matches != null) {
|
||||
val jumptarget = matches.groups[2]?.value
|
||||
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
|
||||
val node = program.namespace.lookup(jumptarget.split('.'), context)
|
||||
if (node is Subroutine) {
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
} else if (jumptarget.contains('.')) {
|
||||
// maybe only the first part already refers to a subroutine
|
||||
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
|
||||
if (node2 is Subroutine) {
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node2)
|
||||
calledBy[node2] = calledBy.getValue(node2).plus(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val matches2 = asmRefRx.matchEntire(line)
|
||||
if (matches2 != null) {
|
||||
val target = matches2.groups[2]?.value
|
||||
if (target != null && (target[0].isLetter() || target[0] == '_')) {
|
||||
if (target.contains('.')) {
|
||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||
if (node is Subroutine) {
|
||||
if(scope!=null)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkRecursiveCalls(errors: ErrorReporter) {
|
||||
fun checkRecursiveCalls(errors: IErrorReporter) {
|
||||
val cycles = recursionCycles()
|
||||
if(cycles.any()) {
|
||||
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)
|
||||
@ -263,4 +163,39 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
recStack[sub] = false
|
||||
return false
|
||||
}
|
||||
|
||||
fun unused(module: Module) = module !in usedModules
|
||||
|
||||
fun unused(sub: Subroutine): Boolean {
|
||||
return sub !in usedSubroutines && !nameInAssemblyCode(sub.name)
|
||||
}
|
||||
|
||||
fun unused(block: Block): Boolean {
|
||||
return block !in usedBlocks && !nameInAssemblyCode(block.name)
|
||||
}
|
||||
|
||||
fun unused(decl: VarDecl): Boolean {
|
||||
if(decl.type!=VarDeclType.VAR || decl.datatype==DataType.STRUCT || decl.autogeneratedDontRemove || decl.parent is StructDecl)
|
||||
return false
|
||||
|
||||
if(decl.definingBlock() !in usedBlocks)
|
||||
return false
|
||||
|
||||
val allReferencedVardecls = allIdentifiersAndTargets.filter { it.value is VarDecl }.map { it.value }.toSet()
|
||||
return decl !in allReferencedVardecls && !nameInAssemblyCode(decl.name)
|
||||
}
|
||||
|
||||
private fun nameInAssemblyCode(name: String) = allAssemblyNodes.any { it.assembly.contains(name) }
|
||||
|
||||
inline fun unused(label: Label) = false // just always output labels
|
||||
|
||||
fun unused(stmt: ISymbolStatement): Boolean {
|
||||
return when(stmt) {
|
||||
is Subroutine -> unused(stmt)
|
||||
is Block -> unused(stmt)
|
||||
is VarDecl -> unused(stmt)
|
||||
is Label -> false // just always output labels
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,28 +9,32 @@ import kotlin.math.pow
|
||||
class ConstExprEvaluator {
|
||||
|
||||
fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression {
|
||||
return when(operator) {
|
||||
"+" -> plus(left, right)
|
||||
"-" -> minus(left, right)
|
||||
"*" -> multiply(left, right)
|
||||
"/" -> divide(left, right)
|
||||
"%" -> remainder(left, right)
|
||||
"**" -> power(left, right)
|
||||
"&" -> bitwiseand(left, right)
|
||||
"|" -> bitwiseor(left, right)
|
||||
"^" -> bitwisexor(left, right)
|
||||
"and" -> logicaland(left, right)
|
||||
"or" -> logicalor(left, right)
|
||||
"xor" -> logicalxor(left, right)
|
||||
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
|
||||
">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
|
||||
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
|
||||
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
|
||||
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
|
||||
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
|
||||
"<<" -> shiftedleft(left, right)
|
||||
">>" -> shiftedright(left, right)
|
||||
else -> throw FatalAstException("const evaluation for invalid operator $operator")
|
||||
try {
|
||||
return when(operator) {
|
||||
"+" -> plus(left, right)
|
||||
"-" -> minus(left, right)
|
||||
"*" -> multiply(left, right)
|
||||
"/" -> divide(left, right)
|
||||
"%" -> remainder(left, right)
|
||||
"**" -> power(left, right)
|
||||
"&" -> bitwiseand(left, right)
|
||||
"|" -> bitwiseor(left, right)
|
||||
"^" -> bitwisexor(left, right)
|
||||
"and" -> logicaland(left, right)
|
||||
"or" -> logicalor(left, right)
|
||||
"xor" -> logicalxor(left, right)
|
||||
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
|
||||
">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
|
||||
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
|
||||
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
|
||||
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
|
||||
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
|
||||
"<<" -> shiftedleft(left, right)
|
||||
">>" -> shiftedright(left, right)
|
||||
else -> throw FatalAstException("const evaluation for invalid operator $operator")
|
||||
}
|
||||
} catch (ax: FatalAstException) {
|
||||
throw ExpressionError(ax.message, left.position)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,8 +13,7 @@ import prog8.compiler.target.ICompilationTarget
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// @( &thing ) --> thing
|
||||
@ -224,7 +223,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
||||
range.step
|
||||
}
|
||||
|
||||
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, ICompilationTarget.instance, 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.
|
||||
|
@ -10,12 +10,11 @@ import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.ErrorReporter
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
// 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() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
try {
|
||||
@ -39,8 +38,7 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
|
||||
// Replace all constant identifiers with their actual value,
|
||||
// and the array var initializer values and sizes.
|
||||
// This is needed because further constant optimizations depend on those.
|
||||
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
// replace identifiers that refer to const value, with the value itself
|
||||
@ -75,7 +73,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true) {
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return noModifications
|
||||
}
|
||||
@ -93,19 +91,6 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
decl
|
||||
))
|
||||
}
|
||||
} else if(arraysize.constIndex()==null) {
|
||||
// see if we can calculate the size from other fields
|
||||
try {
|
||||
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
|
||||
if (cval != null) {
|
||||
arraysize.indexVar = null
|
||||
arraysize.origExpression = null
|
||||
arraysize.indexNum = cval
|
||||
}
|
||||
} catch (x: UndefinedSymbolError) {
|
||||
errors.err(x.message, x.position)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,7 +177,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
|
||||
if(rangeExpr==null && litval!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = litval.number.toDouble()
|
||||
if (fillvalue < ICompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || fillvalue > ICompilationTarget.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)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
|
@ -25,7 +25,6 @@ import kotlin.math.pow
|
||||
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
@ -491,9 +490,9 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if(!idt.isKnown)
|
||||
throw FatalAstException("unknown dt")
|
||||
return NumericLiteralValue(idt.typeOrElse(DataType.STRUCT), 0, expr.position)
|
||||
} else if (cv == 2.0) {
|
||||
} else if (cv in powersOfTwo) {
|
||||
expr.operator = "&"
|
||||
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
|
||||
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -2,31 +2,32 @@ package prog8.optimizer
|
||||
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.ErrorReporter
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
|
||||
internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
|
||||
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
if(errors.noErrors()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
val replacer = ConstantIdentifierReplacer(this, errors)
|
||||
val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
|
||||
replacer.visit(this)
|
||||
if (errors.isEmpty()) {
|
||||
if (errors.noErrors()) {
|
||||
replacer.applyModifications()
|
||||
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
if(errors.noErrors()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
val optimizer = ConstantFoldingOptimizer(this)
|
||||
val optimizer = ConstantFoldingOptimizer(this, compTarget)
|
||||
optimizer.visit(this)
|
||||
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
|
||||
while (errors.noErrors() && optimizer.applyModifications() > 0) {
|
||||
optimizer.visit(this)
|
||||
}
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
if (errors.noErrors()) {
|
||||
replacer.visit(this)
|
||||
replacer.applyModifications()
|
||||
}
|
||||
@ -34,13 +35,15 @@ internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
}
|
||||
}
|
||||
|
||||
if(errors.isEmpty())
|
||||
if(errors.noErrors())
|
||||
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.optimizeStatements(errors: ErrorReporter, functions: IBuiltinFunctions): Int {
|
||||
val optimizer = StatementOptimizer(this, errors, functions)
|
||||
internal fun Program.optimizeStatements(errors: IErrorReporter,
|
||||
functions: IBuiltinFunctions,
|
||||
compTarget: ICompilationTarget): Int {
|
||||
val optimizer = StatementOptimizer(this, errors, functions, compTarget)
|
||||
optimizer.visit(this)
|
||||
val optimizationCount = optimizer.applyModifications()
|
||||
|
||||
@ -55,8 +58,8 @@ internal fun Program.simplifyExpressions() : Int {
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.splitBinaryExpressions() : Int {
|
||||
val opti = BinExprSplitter(this)
|
||||
internal fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int {
|
||||
val opti = BinExprSplitter(this, compTarget)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
@ -10,64 +10,27 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.compiler.ErrorReporter
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
import kotlin.math.floor
|
||||
|
||||
internal const val retvarName = "prog8_retval"
|
||||
|
||||
|
||||
internal class StatementOptimizer(private val program: Program,
|
||||
private val errors: ErrorReporter,
|
||||
private val functions: IBuiltinFunctions
|
||||
) : AstWalker() {
|
||||
private val errors: IErrorReporter,
|
||||
private val functions: IBuiltinFunctions,
|
||||
private val compTarget: ICompilationTarget) : AstWalker() {
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
private val callgraph = CallGraph(program)
|
||||
private val subsThatNeedReturnVariable = mutableSetOf<Triple<INameScope, DataType, Position>>()
|
||||
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
|
||||
if (block !in callgraph.usedSymbols) {
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
||||
if(subroutine.asmAddress==null && !forceOutput) {
|
||||
if(subroutine.containsNoCodeNorVars() && !subroutine.inline) {
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
val removals = callgraph.calledBy.getValue(subroutine).map {
|
||||
IAstModification.Remove(it, it.definingScope())
|
||||
}.toMutableList()
|
||||
removals += IAstModification.Remove(subroutine, subroutine.definingScope())
|
||||
return removals
|
||||
}
|
||||
for(returnvar in subsThatNeedReturnVariable) {
|
||||
val decl = VarDecl(VarDeclType.VAR, returnvar.second, ZeropageWish.DONTCARE, null, retvarName, null, null, false, true, returnvar.third)
|
||||
returnvar.first.statements.add(0, decl)
|
||||
}
|
||||
|
||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
||||
if(decl.type == VarDeclType.VAR)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
|
||||
return listOf(IAstModification.Remove(decl, decl.definingScope()))
|
||||
}
|
||||
|
||||
subsThatNeedReturnVariable.clear()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -81,11 +44,9 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
// 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("c64scr", "print")) {
|
||||
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
|
||||
val arg = functionCallStatement.args.single()
|
||||
val stringVar: IdentifierReference?
|
||||
stringVar = if(arg is AddressOf) {
|
||||
val stringVar: IdentifierReference? = if(arg is AddressOf) {
|
||||
arg.identifier
|
||||
} else {
|
||||
arg as? IdentifierReference
|
||||
@ -96,29 +57,29 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if(string!=null) {
|
||||
val pos = functionCallStatement.position
|
||||
if (string.value.length == 1) {
|
||||
val firstCharEncoded = ICompilationTarget.instance.encodeString(string.value, string.altEncoding)[0]
|
||||
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
val chrout = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||
} else if (string.value.length == 2) {
|
||||
val firstTwoCharsEncoded = ICompilationTarget.instance.encodeString(string.value.take(2), string.altEncoding)
|
||||
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val chrout2 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
IdentifierReference(listOf("txt", "chrout"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val anonscope = AnonymousScope(mutableListOf(), pos)
|
||||
anonscope.statements.add(chrout1)
|
||||
anonscope.statements.add(chrout2)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
|
||||
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -212,7 +173,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
val size = sv.value.length
|
||||
if(size==1) {
|
||||
// loop over string of length 1 -> just assign the single character
|
||||
val character = ICompilationTarget.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 scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
|
||||
@ -293,18 +254,6 @@ internal class StatementOptimizer(private val program: Program,
|
||||
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> {
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.definingScope()
|
||||
@ -402,7 +351,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
||||
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(rightCv.toInt()) {
|
||||
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
||||
incs.statements.add(PostIncrDecr(assignment.target.copy(), "++", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
||||
}
|
||||
@ -416,7 +365,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(rightCv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
||||
decs.statements.add(PostIncrDecr(assignment.target.copy(), "--", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
|
||||
}
|
||||
@ -444,21 +393,17 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
fun returnViaIntermediary(value: Expression): Iterable<IAstModification>? {
|
||||
val returnDt = returnStmt.definingSubroutine()!!.returntypes.single()
|
||||
fun returnViaIntermediaryVar(value: Expression): Iterable<IAstModification>? {
|
||||
val subr = returnStmt.definingSubroutine()!!
|
||||
val returnDt = subr.returntypes.single()
|
||||
if (returnDt in IntegerDatatypes) {
|
||||
// first assign to intermediary, then return that register
|
||||
val returnValueIntermediary =
|
||||
when(returnDt) {
|
||||
DataType.UBYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_ub"), returnStmt.position)
|
||||
DataType.BYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_b"), returnStmt.position)
|
||||
DataType.UWORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_uw"), returnStmt.position)
|
||||
DataType.WORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_w"), returnStmt.position)
|
||||
else -> throw FatalAstException("weird return dt")
|
||||
}
|
||||
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
|
||||
// first assign to intermediary variable, then return that
|
||||
subsThatNeedReturnVariable.add(Triple(subr, returnDt, returnStmt.position))
|
||||
val returnValueIntermediary1 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
||||
val returnValueIntermediary2 = IdentifierReference(listOf(retvarName), returnStmt.position)
|
||||
val tgt = AssignTarget(returnValueIntermediary1, null, null, returnStmt.position)
|
||||
val assign = Assignment(tgt, value, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary, returnStmt.position)
|
||||
val returnReplacement = Return(returnValueIntermediary2, returnStmt.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
|
||||
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
|
||||
@ -469,12 +414,12 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
when(returnStmt.value) {
|
||||
is PrefixExpression -> {
|
||||
val mod = returnViaIntermediary(returnStmt.value!!)
|
||||
val mod = returnViaIntermediaryVar(returnStmt.value!!)
|
||||
if(mod!=null)
|
||||
return mod
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
val mod = returnViaIntermediary(returnStmt.value!!)
|
||||
val mod = returnViaIntermediaryVar(returnStmt.value!!)
|
||||
if(mod!=null)
|
||||
return mod
|
||||
}
|
||||
|
96
compiler/src/prog8/optimizer/SubroutineInliner.kt
Normal file
96
compiler/src/prog8/optimizer/SubroutineInliner.kt
Normal file
@ -0,0 +1,96 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.IErrorReporter
|
||||
|
||||
|
||||
internal class SubroutineInliner(private val program: Program, val errors: IErrorReporter, private val compilerOptions: CompilationOptions): AstWalker() {
|
||||
private var callsToInlinedSubroutines = mutableListOf<Pair<IFunctionCall, Node>>()
|
||||
|
||||
fun fixCallsToInlinedSubroutines() {
|
||||
for((call, parent) in callsToInlinedSubroutines) {
|
||||
val sub = call.target.targetSubroutine(program)!!
|
||||
val intermediateReturnValueVar = sub.statements.filterIsInstance<VarDecl>().singleOrNull { it.name.endsWith(retvarName) }
|
||||
if(intermediateReturnValueVar!=null) {
|
||||
val scope = parent.definingScope()
|
||||
if(!scope.statements.filterIsInstance<VarDecl>().any { it.name==intermediateReturnValueVar.name}) {
|
||||
val decl = intermediateReturnValueVar.copy()
|
||||
scope.statements.add(0, decl)
|
||||
decl.linkParents(scope as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
return if(compilerOptions.optimize && subroutine.inline && !subroutine.isAsmSubroutine)
|
||||
annotateInlinedSubroutineIdentifiers(subroutine)
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
return after(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
return after(functionCall as IFunctionCall, parent, functionCall.position)
|
||||
}
|
||||
|
||||
private fun after(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
|
||||
val sub = functionCall.target.targetSubroutine(program)
|
||||
if(sub != null && compilerOptions.optimize && sub.inline && !sub.isAsmSubroutine)
|
||||
callsToInlinedSubroutines.add(Pair(functionCall, parent))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun annotateInlinedSubroutineIdentifiers(sub: Subroutine): List<IAstModification> {
|
||||
// this adds name prefixes to the identifiers used in the subroutine,
|
||||
// so that the statements can be inlined (=copied) in the call site and still reference
|
||||
// the correct symbols as seen from the scope of the subroutine.
|
||||
|
||||
class Annotator: AstWalker() {
|
||||
var numReturns=0
|
||||
|
||||
override fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
val stmt = identifier.targetStatement(program)!!
|
||||
if(stmt is BuiltinFunctionStatementPlaceholder)
|
||||
return noModifications
|
||||
|
||||
val prefixed = stmt.makeScopedName(identifier.nameInSource.last()).split('.')
|
||||
val withPrefix = IdentifierReference(prefixed, identifier.position)
|
||||
return listOf(IAstModification.ReplaceNode(identifier, withPrefix, parent))
|
||||
}
|
||||
|
||||
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
numReturns++
|
||||
if(parent !== sub || sub.indexOfChild(returnStmt)<sub.statements.size-1)
|
||||
errors.err("return statement must be the very last statement in the inlined subroutine", sub.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
fun theModifications(): List<IAstModification> {
|
||||
return this.modifications.map { it.first }.toList()
|
||||
}
|
||||
}
|
||||
|
||||
val annotator = Annotator()
|
||||
sub.accept(annotator, sub.parent)
|
||||
if(annotator.numReturns>1) {
|
||||
errors.err("inlined subroutine can only have one return statement", sub.position)
|
||||
return noModifications
|
||||
}
|
||||
return annotator.theModifications()
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.ErrorReporter
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.PrefixExpression
|
||||
@ -11,40 +12,23 @@ import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.compiler.IErrorReporter
|
||||
import prog8.compiler.target.ICompilationTarget
|
||||
|
||||
|
||||
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): AstWalker() {
|
||||
|
||||
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
|
||||
val callgraph = CallGraph(program)
|
||||
val removals = mutableListOf<IAstModification>()
|
||||
private val callgraph = CallGraph(program)
|
||||
|
||||
// remove all subroutines that aren't called, or are empty
|
||||
val entrypoint = program.entrypoint()
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
|
||||
removals.add(IAstModification.Remove(sub, sub.definingScope()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||
removals.add(IAstModification.Remove(block, block.definingScope()))
|
||||
}
|
||||
|
||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||
program.modules.forEach {
|
||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||
removals.add(IAstModification.Remove(it, it.definingScope()))
|
||||
}
|
||||
|
||||
return removals
|
||||
override fun before(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
return if (!module.isLibraryModule && (module.containsNoCodeNorVars() || callgraph.unused(module)))
|
||||
listOf(IAstModification.Remove(module, module.definingScope()))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(breakStmt, parent as INameScope)
|
||||
return emptyList()
|
||||
@ -79,15 +63,58 @@ internal class UnusedCodeRemover(private val program: Program, private val error
|
||||
}
|
||||
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
if(block.name != program.internedStringsModuleName)
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
if(callgraph.unused(block)) {
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return listOf(IAstModification.Remove(block, parent as INameScope))
|
||||
}
|
||||
}
|
||||
|
||||
val removeDoubleAssignments = deduplicateAssignments(block.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
||||
if (subroutine !== program.entrypoint() && !forceOutput && !subroutine.inline && !subroutine.isAsmSubroutine) {
|
||||
if(callgraph.unused(subroutine)) {
|
||||
if(!subroutine.definingModule().isLibraryModule)
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
|
||||
}
|
||||
if(subroutine.containsNoCodeNorVars()) {
|
||||
if(!subroutine.definingModule().isLibraryModule)
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
val removals = mutableListOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
|
||||
callgraph.calledBy[subroutine]?.let {
|
||||
for(node in it)
|
||||
removals.add(IAstModification.Remove(node, node.definingScope()))
|
||||
}
|
||||
return removals
|
||||
}
|
||||
}
|
||||
|
||||
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||
if(!forceOutput && !decl.autogeneratedDontRemove && callgraph.unused(decl)) {
|
||||
if(decl.type == VarDeclType.VAR && !decl.definingBlock().isInLibrary)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
|
||||
return listOf(IAstModification.Remove(decl, decl.definingScope()))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
|
||||
// removes 'duplicate' assignments that assign the same target directly after another
|
||||
val linesToRemove = mutableListOf<Assignment>()
|
||||
@ -96,7 +123,7 @@ internal class UnusedCodeRemover(private val program: Program, private val error
|
||||
val assign1 = stmtPairs[0] as? Assignment
|
||||
val assign2 = stmtPairs[1] as? Assignment
|
||||
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
|
||||
if (assign1.target.isSameAs(assign2.target, program) && ICompilationTarget.instance.isInRegularRAM(assign1.target, program)) {
|
||||
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())))
|
||||
// only remove the second assignment if its value is a simple expression!
|
||||
when(assign2.value) {
|
||||
|
@ -5,21 +5,22 @@ import org.hamcrest.Matchers.closeTo
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
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.statements.*
|
||||
import prog8.ast.toHex
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import prog8.compiler.target.cbm.Petscii
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
|
||||
import java.io.CharConversionException
|
||||
import java.nio.file.Path
|
||||
import kotlin.test.*
|
||||
@ -134,7 +135,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
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)
|
||||
@ -147,37 +148,37 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
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> {
|
||||
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> {
|
||||
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)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpModesWithFloats() {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
|
||||
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> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
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)
|
||||
assertEquals(0, zp.available())
|
||||
assertFailsWith<CompilerException> {
|
||||
@ -187,19 +188,19 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
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())
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
|
||||
assertEquals(89, zp2.available())
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
|
||||
assertEquals(85, zp2.available())
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
|
||||
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())
|
||||
}
|
||||
|
||||
@Test
|
||||
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())
|
||||
assertTrue(50 in zp1.free)
|
||||
assertTrue(100 in zp1.free)
|
||||
@ -208,7 +209,7 @@ class TestC64Zeropage {
|
||||
assertTrue(200 in zp1.free)
|
||||
assertTrue(255 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())
|
||||
assertFalse(50 in zp2.free)
|
||||
assertFalse(100 in zp2.free)
|
||||
@ -221,7 +222,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
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())
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
@ -244,7 +245,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
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())
|
||||
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||
assertTrue(loc > 3)
|
||||
@ -274,7 +275,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
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(0x04, zp.allocate("", DataType.WORD, null, errors))
|
||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
@ -293,7 +294,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
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")
|
||||
}
|
||||
}
|
||||
@ -303,9 +304,30 @@ class TestC64Zeropage {
|
||||
class TestCx16Zeropage {
|
||||
@Test
|
||||
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")
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -326,8 +348,8 @@ class TestPetscii {
|
||||
listOf<Short>(72, 69, 76, 76, 79, 32, 0xd7, 0xcf, 0xd2, 0xcc, 0xc4, 32, 49, 50, 51, 32, 64, 33, 0x5c)))
|
||||
assertThat(Petscii.encodePetscii("\uf11a", true), equalTo(listOf<Short>(0x12))) // reverse vid
|
||||
assertThat(Petscii.encodePetscii("✓", true), equalTo(listOf<Short>(0xfa)))
|
||||
assertFailsWith<CharConversionException> { Petscii.encodePetscii("π", true) }
|
||||
assertFailsWith<CharConversionException> { Petscii.encodePetscii("♥", true) }
|
||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("π", true), equalTo(listOf<Short>(255)))
|
||||
assertThat("expect lowercase error fallback", Petscii.encodePetscii("♥", true), equalTo(listOf<Short>(0xd3)))
|
||||
|
||||
assertThat(Petscii.decodePetscii(listOf(72, 0xd7, 0x5c, 0xfa, 0x12), true), equalTo("hW£✓\uF11A"))
|
||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1), true) }
|
||||
@ -341,7 +363,7 @@ class TestPetscii {
|
||||
assertThat(Petscii.encodePetscii("\uf11a"), equalTo(listOf<Short>(0x12))) // reverse vid
|
||||
assertThat(Petscii.encodePetscii("♥"), equalTo(listOf<Short>(0xd3)))
|
||||
assertThat(Petscii.encodePetscii("π"), equalTo(listOf<Short>(0xff)))
|
||||
assertFailsWith<CharConversionException> { Petscii.encodePetscii("✓") }
|
||||
assertThat("expecting fallback", Petscii.encodePetscii("✓"), equalTo(listOf<Short>(250)))
|
||||
|
||||
assertThat(Petscii.decodePetscii(listOf(72, 0x5c, 0xd3, 0xff)), equalTo("H£♥π"))
|
||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodePetscii(listOf(-1)) }
|
||||
@ -354,8 +376,8 @@ class TestPetscii {
|
||||
listOf<Short>(0x08, 0x05, 0x0c, 0x0c, 0x0f, 0x20, 0x57, 0x4f, 0x52, 0x4c, 0x44, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)
|
||||
))
|
||||
assertThat(Petscii.encodeScreencode("✓", true), equalTo(listOf<Short>(0x7a)))
|
||||
assertFailsWith<CharConversionException> { Petscii.encodeScreencode("♥", true) }
|
||||
assertFailsWith<CharConversionException> { Petscii.encodeScreencode("π", true) }
|
||||
assertThat("expect fallback", Petscii.encodeScreencode("♥", true), equalTo(listOf<Short>(83)))
|
||||
assertThat("expect fallback", Petscii.encodeScreencode("π", true), equalTo(listOf<Short>(94)))
|
||||
|
||||
assertThat(Petscii.decodeScreencode(listOf(0x08, 0x57, 0x1c, 0x7a), true), equalTo("hW£✓"))
|
||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1), true) }
|
||||
@ -368,8 +390,9 @@ class TestPetscii {
|
||||
listOf<Short>(0x17, 0x0f, 0x12, 0x0c, 0x04, 0x20, 0x31, 0x32, 0x33, 0x20, 0x00, 0x21, 0x1c)))
|
||||
assertThat(Petscii.encodeScreencode("♥"), equalTo(listOf<Short>(0x53)))
|
||||
assertThat(Petscii.encodeScreencode("π"), equalTo(listOf<Short>(0x5e)))
|
||||
assertFailsWith<CharConversionException> { Petscii.encodeScreencode("✓") }
|
||||
assertFailsWith<CharConversionException> { Petscii.encodeScreencode("hello") }
|
||||
assertThat(Petscii.encodeScreencode("HELLO"), equalTo(listOf<Short>(8, 5, 12, 12, 15)))
|
||||
assertThat("expecting fallback", Petscii.encodeScreencode("hello"), equalTo(listOf<Short>(8, 5, 12, 12, 15)))
|
||||
assertThat("expecting fallback", Petscii.encodeScreencode("✓"), equalTo(listOf<Short>(122)))
|
||||
|
||||
assertThat(Petscii.decodeScreencode(listOf(0x17, 0x1c, 0x53, 0x5e)), equalTo("W£♥π"))
|
||||
assertFailsWith<ArrayIndexOutOfBoundsException> { Petscii.decodeScreencode(listOf(-1)) }
|
||||
@ -408,16 +431,20 @@ 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): NumericLiteralValue? = null
|
||||
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
|
||||
fun testInValidRamC64_memory_addresses() {
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val program = Program("test", mutableListOf(), DummyFunctions())
|
||||
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||
@ -442,7 +469,7 @@ class TestMemory {
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val program = Program("test", mutableListOf(), DummyFunctions())
|
||||
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
|
||||
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
|
||||
@ -461,7 +488,7 @@ class TestMemory {
|
||||
@Test
|
||||
fun testInValidRamC64_memory_identifiers() {
|
||||
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
|
||||
val program = Program("test", mutableListOf(), DummyFunctions())
|
||||
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
|
||||
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
|
||||
@ -490,7 +517,7 @@ class TestMemory {
|
||||
fun testInValidRamC64_memory_expression() {
|
||||
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val program = Program("test", mutableListOf(), DummyFunctions())
|
||||
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
|
||||
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
|
||||
@ -501,7 +528,7 @@ class TestMemory {
|
||||
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 module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions())
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
@ -514,7 +541,7 @@ class TestMemory {
|
||||
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 module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions())
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
@ -527,7 +554,7 @@ class TestMemory {
|
||||
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 module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions())
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
@ -540,7 +567,7 @@ class TestMemory {
|
||||
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 module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions())
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
@ -554,7 +581,7 @@ class TestMemory {
|
||||
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 module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions())
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
assertTrue(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
@ -568,7 +595,7 @@ class TestMemory {
|
||||
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 module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions())
|
||||
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
|
||||
module.linkParents(ParentSentinel)
|
||||
assertFalse(C64Target.isInRegularRAM(target, program))
|
||||
}
|
||||
|
13
compiler/test/arithmetic/Makefile
Normal file
13
compiler/test/arithmetic/Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
.PHONY: all clean test
|
||||
|
||||
all: test
|
||||
|
||||
clean:
|
||||
rm -f *.prg *.asm *.vice-*
|
||||
|
||||
test: clean
|
||||
p8compile -target cx16 *.p8 >/dev/null
|
||||
for program in *.prg; do \
|
||||
echo "RUNNING:" $$program ; \
|
||||
x16emu -run -prg $$program >/dev/null ; \
|
||||
done
|
18
compiler/test/comparisons/Makefile
Normal file
18
compiler/test/comparisons/Makefile
Normal file
@ -0,0 +1,18 @@
|
||||
.PHONY: all clean test
|
||||
|
||||
all: test
|
||||
|
||||
clean:
|
||||
rm -f *.prg *.asm *.vice-* test_*.p8
|
||||
|
||||
test: clean generate test_prgs
|
||||
|
||||
generate:
|
||||
python make_tests.py
|
||||
p8compile -noopt -target cx16 *.p8 >/dev/null
|
||||
|
||||
test_prgs:
|
||||
for program in *.prg; do \
|
||||
echo "RUNNING:" $$program ; \
|
||||
x16emu -run -prg $$program >/dev/null ; \
|
||||
done
|
508
compiler/test/comparisons/make_tests.py
Normal file
508
compiler/test/comparisons/make_tests.py
Normal file
@ -0,0 +1,508 @@
|
||||
# generates various Prog8 files with a huge amount of number comparion tests,
|
||||
# for all supported datatypes and all comparison operators.
|
||||
|
||||
import sys
|
||||
|
||||
index = 0
|
||||
|
||||
|
||||
def minmaxvalues(dt):
|
||||
if dt == "ubyte":
|
||||
return 0, 255
|
||||
elif dt == "uword":
|
||||
return 0, 65535
|
||||
elif dt == "byte":
|
||||
return -128, 127
|
||||
elif dt == "word":
|
||||
return -32768, 32767
|
||||
elif dt == "float":
|
||||
return -99999999, 99999999
|
||||
else:
|
||||
raise ValueError(dt)
|
||||
|
||||
|
||||
def gen_test(dt, comparison, left, right, expected):
|
||||
global index
|
||||
etxt = f"{left} {comparison} {right}"
|
||||
if eval(etxt) != expected:
|
||||
raise ValueError("invalid comparison: "+etxt+" for "+dt)
|
||||
if expected:
|
||||
stmt_ok = lambda ix: "num_successes++"
|
||||
stmt_else = lambda ix: f"error({ix})"
|
||||
else:
|
||||
stmt_ok = lambda ix: f"error({ix})"
|
||||
stmt_else = lambda ix: "num_successes++"
|
||||
|
||||
def c(number):
|
||||
if dt not in ("byte", "ubyte"):
|
||||
return f"({number} as {dt})"
|
||||
return str(number)
|
||||
|
||||
print(
|
||||
f""" left = {c(left)}
|
||||
right = {c(right)}
|
||||
"""
|
||||
)
|
||||
|
||||
# const <op> const
|
||||
index += 1
|
||||
print(
|
||||
f""" ; test #{index}
|
||||
if {c(left)} {comparison} {c(right)} {{
|
||||
{stmt_ok(index)}
|
||||
}} else {{
|
||||
{stmt_else(index)}
|
||||
}}
|
||||
""")
|
||||
# const <op> var
|
||||
index += 1
|
||||
print(
|
||||
f""" ; test #{index}
|
||||
if {c(left)} {comparison} right {{
|
||||
{stmt_ok(index)}
|
||||
}} else {{
|
||||
{stmt_else(index)}
|
||||
}}
|
||||
""")
|
||||
# const <op> expr
|
||||
index += 1
|
||||
print(
|
||||
f""" ; test #{index}
|
||||
if {c(left)} {comparison} right+zero {{
|
||||
{stmt_ok(index)}
|
||||
}} else {{
|
||||
{stmt_else(index)}
|
||||
}}
|
||||
""")
|
||||
# var <op> const
|
||||
index += 1
|
||||
print(
|
||||
f""" ; test #{index}
|
||||
if left {comparison} {c(right)} {{
|
||||
{stmt_ok(index)}
|
||||
}} else {{
|
||||
{stmt_else(index)}
|
||||
}}
|
||||
""")
|
||||
# var <op> var
|
||||
index += 1
|
||||
print(
|
||||
f""" ; test #{index}
|
||||
if left {comparison} right {{
|
||||
{stmt_ok(index)}
|
||||
}} else {{
|
||||
{stmt_else(index)}
|
||||
}}
|
||||
""")
|
||||
# var <op> expr
|
||||
index += 1
|
||||
print(
|
||||
f""" ; test #{index}
|
||||
if left {comparison} right+zero {{
|
||||
{stmt_ok(index)}
|
||||
}} else {{
|
||||
{stmt_else(index)}
|
||||
}}
|
||||
""")
|
||||
# expr <op> const
|
||||
index += 1
|
||||
print(
|
||||
f""" ; test #{index}
|
||||
if left+zero {comparison} {c(right)} {{
|
||||
{stmt_ok(index)}
|
||||
}} else {{
|
||||
{stmt_else(index)}
|
||||
}}
|
||||
""")
|
||||
# expr <op> var
|
||||
index += 1
|
||||
print(
|
||||
f""" ; test #{index}
|
||||
if left+zero {comparison} right {{
|
||||
{stmt_ok(index)}
|
||||
}} else {{
|
||||
{stmt_else(index)}
|
||||
}}
|
||||
""")
|
||||
# expr <op> expr
|
||||
index += 1
|
||||
print(
|
||||
f""" ; test #{index}
|
||||
if left+zero {comparison} right+zero {{
|
||||
{stmt_ok(index)}
|
||||
}} else {{
|
||||
{stmt_else(index)}
|
||||
}}
|
||||
""")
|
||||
|
||||
|
||||
def gen_comp_header(dt, operator):
|
||||
print(" ; tests: ", dt, operator)
|
||||
print(" comparison = \""+operator+"\"")
|
||||
print(" txt.print(datatype)")
|
||||
print(" txt.spc()")
|
||||
print(" txt.print(comparison)")
|
||||
print(" txt.nl()")
|
||||
|
||||
|
||||
def gen_comp_equal(dt):
|
||||
minval, maxval = minmaxvalues(dt)
|
||||
gen_comp_header(dt, "==")
|
||||
gen_test(dt, "==", 0, 0, True)
|
||||
gen_test(dt, "==", 0, 1, False)
|
||||
gen_test(dt, "==", 100, 100, True)
|
||||
gen_test(dt, "==", 100, 101, False)
|
||||
if maxval >= 200:
|
||||
gen_test(dt, "==", 200, 200, True)
|
||||
gen_test(dt, "==", 200, 201, False)
|
||||
if maxval >= 9999:
|
||||
gen_test(dt, "==", 9999, 9999, True)
|
||||
gen_test(dt, "==", 9999, 10000, False)
|
||||
gen_test(dt, "==", 0x5000, 0x5000, True)
|
||||
gen_test(dt, "==", 0x5000, 0x5001, False)
|
||||
gen_test(dt, "==", 0x5000, 0x4fff, False)
|
||||
if maxval >= 30000:
|
||||
gen_test(dt, "==", 30000, 30000, True)
|
||||
gen_test(dt, "==", 30000, 30001, False)
|
||||
if maxval >= 40000:
|
||||
gen_test(dt, "==", 0xf000, 0xf000, True)
|
||||
gen_test(dt, "==", 0xf000, 0xf001, False)
|
||||
gen_test(dt, "==", 0xf000, 0xffff, False)
|
||||
if minval < 0:
|
||||
gen_test(dt, "==", 0, -1, False)
|
||||
gen_test(dt, "==", -100, -100, True)
|
||||
if minval < -200:
|
||||
gen_test(dt, "==", -200, -200, True)
|
||||
gen_test(dt, "==", -200, -201, False)
|
||||
if minval < -9999:
|
||||
gen_test(dt, "==", -0x5000, -0x5000, True)
|
||||
gen_test(dt, "==", -0x5000, -0x5001, False)
|
||||
gen_test(dt, "==", -0x5000, -0x4fff, False)
|
||||
gen_test(dt, "==", -9999, -9999, True)
|
||||
gen_test(dt, "==", -9999, -10000, False)
|
||||
gen_test(dt, "==", minval, minval, True)
|
||||
gen_test(dt, "==", minval, minval+1, False)
|
||||
gen_test(dt, "==", maxval, maxval, True)
|
||||
gen_test(dt, "==", maxval, maxval-1, False)
|
||||
|
||||
|
||||
def gen_comp_notequal(dt):
|
||||
minval, maxval = minmaxvalues(dt)
|
||||
gen_comp_header(dt, "!=")
|
||||
gen_test(dt, "!=", 0, 0, False)
|
||||
gen_test(dt, "!=", 0, 1, True)
|
||||
gen_test(dt, "!=", 100, 100, False)
|
||||
gen_test(dt, "!=", 100, 101, True)
|
||||
if maxval >= 200:
|
||||
gen_test(dt, "!=", 200, 200, False)
|
||||
gen_test(dt, "!=", 200, 201, True)
|
||||
if maxval >= 9999:
|
||||
gen_test(dt, "!=", 9999, 9999, False)
|
||||
gen_test(dt, "!=", 9999, 10000, True)
|
||||
gen_test(dt, "!=", 0x5000, 0x5000, False)
|
||||
gen_test(dt, "!=", 0x5000, 0x5001, True)
|
||||
gen_test(dt, "!=", 0x5000, 0x4fff, True)
|
||||
if maxval >= 30000:
|
||||
gen_test(dt, "!=", 30000, 30000, False)
|
||||
gen_test(dt, "!=", 30000, 30001, True)
|
||||
if maxval >= 40000:
|
||||
gen_test(dt, "!=", 0xf000, 0xf000, False)
|
||||
gen_test(dt, "!=", 0xf000, 0xf001, True)
|
||||
gen_test(dt, "!=", 0xf000, 0xffff, True)
|
||||
if minval < 0:
|
||||
gen_test(dt, "!=", 0, -1, True)
|
||||
gen_test(dt, "!=", -100, -100, False)
|
||||
if minval < -200:
|
||||
gen_test(dt, "!=", -200, -200, False)
|
||||
gen_test(dt, "!=", -200, -201, True)
|
||||
if minval < -9999:
|
||||
gen_test(dt, "!=", -0x5000, -0x5000, False)
|
||||
gen_test(dt, "!=", -0x5000, -0x5001, True)
|
||||
gen_test(dt, "!=", -0x5000, -0x4fff, True)
|
||||
gen_test(dt, "!=", -9999, -9999, False)
|
||||
gen_test(dt, "!=", -9999, -10000, True)
|
||||
gen_test(dt, "!=", minval, minval, False)
|
||||
gen_test(dt, "!=", minval, minval+1, True)
|
||||
gen_test(dt, "!=", maxval, maxval, False)
|
||||
gen_test(dt, "!=", maxval, maxval-1, True)
|
||||
|
||||
|
||||
def gen_comp_less(dt):
|
||||
minval, maxval = minmaxvalues(dt)
|
||||
gen_comp_header(dt, "<")
|
||||
gen_test(dt, "<", 0, 0, False)
|
||||
gen_test(dt, "<", 0, 1, True)
|
||||
gen_test(dt, "<", 100, 100, False)
|
||||
gen_test(dt, "<", 100, 101, True)
|
||||
gen_test(dt, "<", 100, 99, False)
|
||||
if maxval >= 200:
|
||||
gen_test(dt, "<", 200, 200, False)
|
||||
gen_test(dt, "<", 200, 201, True)
|
||||
gen_test(dt, "<", 200, 199, False)
|
||||
if maxval >= 9999:
|
||||
gen_test(dt, "<", 9999, 9999, False)
|
||||
gen_test(dt, "<", 9999, 10000, True)
|
||||
gen_test(dt, "<", 9999, 9998, False)
|
||||
gen_test(dt, "<", 0x5000, 0x5000, False)
|
||||
gen_test(dt, "<", 0x5000, 0x5001, True)
|
||||
gen_test(dt, "<", 0x5000, 0x4fff, False)
|
||||
if maxval >= 30000:
|
||||
gen_test(dt, "<", 30000, 30000, False)
|
||||
gen_test(dt, "<", 30000, 30001, True)
|
||||
gen_test(dt, "<", 30000, 29999, False)
|
||||
if maxval >= 40000:
|
||||
gen_test(dt, "<", 0xf000, 0xf000, False)
|
||||
gen_test(dt, "<", 0xf000, 0xf001, True)
|
||||
gen_test(dt, "<", 0xf000, 0xefff, False)
|
||||
if minval < 0:
|
||||
gen_test(dt, "<", 0, -1, False)
|
||||
gen_test(dt, "<", -100, -100, False)
|
||||
gen_test(dt, "<", -100, -101, False)
|
||||
gen_test(dt, "<", -100, -99, True)
|
||||
if minval < -200:
|
||||
gen_test(dt, "<", -200, -200, False)
|
||||
gen_test(dt, "<", -200, -201, False)
|
||||
gen_test(dt, "<", -200, -199, True)
|
||||
if minval < -9999:
|
||||
gen_test(dt, "<", -0x5000, -0x5000, False)
|
||||
gen_test(dt, "<", -0x5000, -0x5001, False)
|
||||
gen_test(dt, "<", -0x5000, -0x4fff, True)
|
||||
gen_test(dt, "<", -9999, -9999, False)
|
||||
gen_test(dt, "<", -9999, -10000, False)
|
||||
gen_test(dt, "<", -9999, -9998, True)
|
||||
|
||||
|
||||
def gen_comp_greater(dt):
|
||||
minval, maxval = minmaxvalues(dt)
|
||||
gen_comp_header(dt, ">")
|
||||
gen_test(dt, ">", 0, 0, False)
|
||||
gen_test(dt, ">", 0, 1, False)
|
||||
gen_test(dt, ">", 100, 100, False)
|
||||
gen_test(dt, ">", 100, 101, False)
|
||||
gen_test(dt, ">", 100, 99, True)
|
||||
if maxval >= 200:
|
||||
gen_test(dt, ">", 200, 200, False)
|
||||
gen_test(dt, ">", 200, 201, False)
|
||||
gen_test(dt, ">", 200, 199, True)
|
||||
if maxval >= 9999:
|
||||
gen_test(dt, ">", 9999, 9999, False)
|
||||
gen_test(dt, ">", 9999, 10000, False)
|
||||
gen_test(dt, ">", 9999, 9998, True)
|
||||
gen_test(dt, ">", 0x5000, 0x5000, False)
|
||||
gen_test(dt, ">", 0x5000, 0x5001, False)
|
||||
gen_test(dt, ">", 0x5000, 0x4fff, True)
|
||||
if maxval >= 30000:
|
||||
gen_test(dt, ">", 30000, 30000, False)
|
||||
gen_test(dt, ">", 30000, 30001, False)
|
||||
gen_test(dt, ">", 30000, 29999, True)
|
||||
if maxval >= 40000:
|
||||
gen_test(dt, ">", 0xf000, 0xf000, False)
|
||||
gen_test(dt, ">", 0xf000, 0xf001, False)
|
||||
gen_test(dt, ">", 0xf000, 0xefff, True)
|
||||
if minval < 0:
|
||||
gen_test(dt, ">", 0, -1, True)
|
||||
gen_test(dt, ">", -100, -100, False)
|
||||
gen_test(dt, ">", -100, -101, True)
|
||||
gen_test(dt, ">", -100, -99, False)
|
||||
if minval < -200:
|
||||
gen_test(dt, ">", -200, -200, False)
|
||||
gen_test(dt, ">", -200, -201, True)
|
||||
gen_test(dt, ">", -200, -199, False)
|
||||
if minval < -9999:
|
||||
gen_test(dt, ">", -0x5000, -0x5000, False)
|
||||
gen_test(dt, ">", -0x5000, -0x5001, True)
|
||||
gen_test(dt, ">", -0x5000, -0x4fff, False)
|
||||
gen_test(dt, ">", -9999, -9999, False)
|
||||
gen_test(dt, ">", -9999, -10000, True)
|
||||
gen_test(dt, ">", -9999, -9998, False)
|
||||
|
||||
|
||||
def gen_comp_lessequal(dt):
|
||||
minval, maxval = minmaxvalues(dt)
|
||||
gen_comp_header(dt, "<=")
|
||||
gen_test(dt, "<=", 0, 0, True)
|
||||
gen_test(dt, "<=", 0, 1, True)
|
||||
gen_test(dt, "<=", 100, 100, True)
|
||||
gen_test(dt, "<=", 100, 101, True)
|
||||
gen_test(dt, "<=", 100, 99, False)
|
||||
if maxval >= 200:
|
||||
gen_test(dt, "<=", 200, 200, True)
|
||||
gen_test(dt, "<=", 200, 201, True)
|
||||
gen_test(dt, "<=", 200, 199, False)
|
||||
if maxval >= 9999:
|
||||
gen_test(dt, "<=", 9999, 9999, True)
|
||||
gen_test(dt, "<=", 9999, 10000, True)
|
||||
gen_test(dt, "<=", 9999, 9998, False)
|
||||
gen_test(dt, "<=", 0x5000, 0x5000, True)
|
||||
gen_test(dt, "<=", 0x5000, 0x5001, True)
|
||||
gen_test(dt, "<=", 0x5000, 0x4fff, False)
|
||||
if maxval >= 30000:
|
||||
gen_test(dt, "<=", 30000, 30000, True)
|
||||
gen_test(dt, "<=", 30000, 30001, True)
|
||||
gen_test(dt, "<=", 30000, 29999, False)
|
||||
if maxval >= 40000:
|
||||
gen_test(dt, "<=", 0xf000, 0xf000, True)
|
||||
gen_test(dt, "<=", 0xf000, 0xf001, True)
|
||||
gen_test(dt, "<=", 0xf000, 0xefff, False)
|
||||
if minval < 0:
|
||||
gen_test(dt, "<=", 0, -1, False)
|
||||
gen_test(dt, "<=", -100, -100, True)
|
||||
gen_test(dt, "<=", -100, -101, False)
|
||||
gen_test(dt, "<=", -100, -99, True)
|
||||
if minval < -200:
|
||||
gen_test(dt, "<=", -200, -200, True)
|
||||
gen_test(dt, "<=", -200, -201, False)
|
||||
gen_test(dt, "<=", -200, -199, True)
|
||||
if minval < -9999:
|
||||
gen_test(dt, "<=", -0x5000, -0x5000, True)
|
||||
gen_test(dt, "<=", -0x5000, -0x5001, False)
|
||||
gen_test(dt, "<=", -0x5000, -0x4fff, True)
|
||||
gen_test(dt, "<=", -9999, -9999, True)
|
||||
gen_test(dt, "<=", -9999, -10000, False)
|
||||
gen_test(dt, "<=", -9999, -9998, True)
|
||||
|
||||
|
||||
def gen_comp_greaterequal(dt):
|
||||
minval, maxval = minmaxvalues(dt)
|
||||
gen_comp_header(dt, ">=")
|
||||
gen_test(dt, ">=", 0, 0, True)
|
||||
gen_test(dt, ">=", 0, 1, False)
|
||||
gen_test(dt, ">=", 100, 100, True)
|
||||
gen_test(dt, ">=", 100, 101, False)
|
||||
gen_test(dt, ">=", 100, 99, True)
|
||||
if maxval >= 200:
|
||||
gen_test(dt, ">=", 200, 200, True)
|
||||
gen_test(dt, ">=", 200, 201, False)
|
||||
gen_test(dt, ">=", 200, 199, True)
|
||||
if maxval >= 9999:
|
||||
gen_test(dt, ">=", 9999, 9999, True)
|
||||
gen_test(dt, ">=", 9999, 10000, False)
|
||||
gen_test(dt, ">=", 9999, 9998, True)
|
||||
gen_test(dt, ">=", 0x5000, 0x5000, True)
|
||||
gen_test(dt, ">=", 0x5000, 0x5001, False)
|
||||
gen_test(dt, ">=", 0x5000, 0x4fff, True)
|
||||
if maxval >= 30000:
|
||||
gen_test(dt, ">=", 30000, 30000, True)
|
||||
gen_test(dt, ">=", 30000, 30001, False)
|
||||
gen_test(dt, ">=", 30000, 29999, True)
|
||||
if maxval >= 40000:
|
||||
gen_test(dt, ">=", 0xf000, 0xf000, True)
|
||||
gen_test(dt, ">=", 0xf000, 0xf001, False)
|
||||
gen_test(dt, ">=", 0xf000, 0xefff, True)
|
||||
if minval < 0:
|
||||
gen_test(dt, ">=", 0, -1, True)
|
||||
gen_test(dt, ">=", -100, -100, True)
|
||||
gen_test(dt, ">=", -100, -101, True)
|
||||
gen_test(dt, ">=", -100, -99, False)
|
||||
if minval < -200:
|
||||
gen_test(dt, ">=", -200, -200, True)
|
||||
gen_test(dt, ">=", -200, -201, True)
|
||||
gen_test(dt, ">=", -200, -199, False)
|
||||
if minval < -9999:
|
||||
gen_test(dt, ">=", -0x5000, -0x5000, True)
|
||||
gen_test(dt, ">=", -0x5000, -0x5001, True)
|
||||
gen_test(dt, ">=", -0x5000, -0x4fff, False)
|
||||
gen_test(dt, ">=", -9999, -9999, True)
|
||||
gen_test(dt, ">=", -9999, -10000, True)
|
||||
gen_test(dt, ">=", -9999, -9998, False)
|
||||
|
||||
|
||||
def generate_test_routine_equalsnotequals(dt):
|
||||
print(f"""
|
||||
sub test_comparisons() {{
|
||||
{dt} left
|
||||
{dt} right
|
||||
{dt} zero = 0
|
||||
""")
|
||||
gen_comp_equal(dt)
|
||||
gen_comp_notequal(dt)
|
||||
print(" }")
|
||||
|
||||
|
||||
def generate_test_routine_lessgreater(dt):
|
||||
print(f"""
|
||||
sub test_comparisons() {{
|
||||
{dt} left
|
||||
{dt} right
|
||||
{dt} zero = 0
|
||||
""")
|
||||
gen_comp_less(dt)
|
||||
gen_comp_greater(dt)
|
||||
print(" }")
|
||||
|
||||
|
||||
def generate_test_routine_lessequalsgreaterequals(dt):
|
||||
print(f"""
|
||||
sub test_comparisons() {{
|
||||
{dt} left
|
||||
{dt} right
|
||||
{dt} zero = 0
|
||||
""")
|
||||
gen_comp_lessequal(dt)
|
||||
gen_comp_greaterequal(dt)
|
||||
print(" }")
|
||||
|
||||
|
||||
def generate(dt, operators):
|
||||
global index
|
||||
index = 0
|
||||
print(f"""
|
||||
%import textio
|
||||
%import floats
|
||||
%import test_stack
|
||||
%zeropage basicsafe
|
||||
|
||||
main {{
|
||||
uword num_errors = 0
|
||||
uword num_successes = 0
|
||||
str datatype = "{dt}"
|
||||
uword comparison
|
||||
|
||||
sub start() {{
|
||||
test_comparisons()
|
||||
print_results()
|
||||
test_stack.test()
|
||||
}}
|
||||
|
||||
sub error(uword index) {{
|
||||
txt.print(" ! error in test ")
|
||||
txt.print_uw(index)
|
||||
txt.chrout(' ')
|
||||
txt.print(datatype)
|
||||
txt.chrout(' ')
|
||||
txt.print(comparison)
|
||||
txt.nl()
|
||||
num_errors++
|
||||
}}
|
||||
""")
|
||||
|
||||
if operators=="eq":
|
||||
generate_test_routine_equalsnotequals(dt)
|
||||
elif operators=="lt":
|
||||
generate_test_routine_lessgreater(dt)
|
||||
elif operators=="lteq":
|
||||
generate_test_routine_lessequalsgreaterequals(dt)
|
||||
else:
|
||||
raise ValueError(operators)
|
||||
|
||||
print(f"""
|
||||
sub print_results() {{
|
||||
txt.nl()
|
||||
txt.print("total {index}: ")
|
||||
txt.print_uw(num_successes)
|
||||
txt.print(" good, ")
|
||||
txt.print_uw(num_errors)
|
||||
txt.print(" errors!\\n")
|
||||
}}
|
||||
}}
|
||||
""")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
for dt in ["ubyte", "uword", "byte", "word", "float"]:
|
||||
sys.stdout = open(f"test_{dt}_eq.p8", "wt")
|
||||
generate(dt, "eq")
|
||||
sys.stdout = open(f"test_{dt}_lt.p8", "wt")
|
||||
generate(dt, "lt")
|
||||
sys.stdout = open(f"test_{dt}_lteq.p8", "wt")
|
||||
generate(dt, "lteq")
|
@ -396,14 +396,18 @@ galaxy10 {
|
||||
sub init(ubyte galaxy10num) {
|
||||
number = 1
|
||||
planet10.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxy10num-1 {
|
||||
nextgalaxy10()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy10() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
@ -576,8 +580,10 @@ galaxy10 {
|
||||
planet10.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
|
||||
planet10.species_kind = (planet10.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet10.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
planet10.goatsoup_seed[0] = lsb(seed[1])
|
||||
planet10.goatsoup_seed[1] = msb(seed[1])
|
||||
planet10.goatsoup_seed[2] = lsb(seed[2])
|
||||
planet10.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
@ -396,14 +396,18 @@ galaxy2 {
|
||||
sub init(ubyte galaxy2num) {
|
||||
number = 1
|
||||
planet2.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxy2num-1 {
|
||||
nextgalaxy2()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy2() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
@ -577,7 +581,10 @@ galaxy2 {
|
||||
planet2.species_kind = (planet2.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet2.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
planet2.goatsoup_seed[0] = lsb(seed[1])
|
||||
planet2.goatsoup_seed[1] = msb(seed[1])
|
||||
planet2.goatsoup_seed[2] = lsb(seed[2])
|
||||
planet2.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
@ -396,14 +396,18 @@ galaxy3 {
|
||||
sub init(ubyte galaxy3num) {
|
||||
number = 1
|
||||
planet3.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxy3num-1 {
|
||||
nextgalaxy3()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy3() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
@ -577,7 +581,10 @@ galaxy3 {
|
||||
planet3.species_kind = (planet3.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet3.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
planet3.goatsoup_seed[0] = lsb(seed[1])
|
||||
planet3.goatsoup_seed[1] = msb(seed[1])
|
||||
planet3.goatsoup_seed[2] = lsb(seed[2])
|
||||
planet3.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
@ -396,14 +396,18 @@ galaxy4 {
|
||||
sub init(ubyte galaxy4num) {
|
||||
number = 1
|
||||
planet4.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxy4num-1 {
|
||||
nextgalaxy4()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy4() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
@ -577,7 +581,10 @@ galaxy4 {
|
||||
planet4.species_kind = (planet4.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet4.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
planet4.goatsoup_seed[0] = lsb(seed[1])
|
||||
planet4.goatsoup_seed[1] = msb(seed[1])
|
||||
planet4.goatsoup_seed[2] = lsb(seed[2])
|
||||
planet4.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
@ -396,14 +396,18 @@ galaxy5 {
|
||||
sub init(ubyte galaxy5num) {
|
||||
number = 1
|
||||
planet5.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxy5num-1 {
|
||||
nextgalaxy5()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy5() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
@ -577,7 +581,10 @@ galaxy5 {
|
||||
planet5.species_kind = (planet5.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet5.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
planet5.goatsoup_seed[0] = lsb(seed[1])
|
||||
planet5.goatsoup_seed[1] = msb(seed[1])
|
||||
planet5.goatsoup_seed[2] = lsb(seed[2])
|
||||
planet5.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
@ -396,14 +396,18 @@ galaxy6 {
|
||||
sub init(ubyte galaxy6num) {
|
||||
number = 1
|
||||
planet6.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxy6num-1 {
|
||||
nextgalaxy6()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy6() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
@ -577,7 +581,10 @@ galaxy6 {
|
||||
planet6.species_kind = (planet6.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet6.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
planet6.goatsoup_seed[0] = lsb(seed[1])
|
||||
planet6.goatsoup_seed[1] = msb(seed[1])
|
||||
planet6.goatsoup_seed[2] = lsb(seed[2])
|
||||
planet6.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
@ -396,14 +396,18 @@ galaxy7 {
|
||||
sub init(ubyte galaxy7num) {
|
||||
number = 1
|
||||
planet7.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxy7num-1 {
|
||||
nextgalaxy7()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy7() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
@ -577,7 +581,10 @@ galaxy7 {
|
||||
planet7.species_kind = (planet7.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet7.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
planet7.goatsoup_seed[0] = lsb(seed[1])
|
||||
planet7.goatsoup_seed[1] = msb(seed[1])
|
||||
planet7.goatsoup_seed[2] = lsb(seed[2])
|
||||
planet7.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
@ -396,14 +396,18 @@ galaxy8 {
|
||||
sub init(ubyte galaxy8num) {
|
||||
number = 1
|
||||
planet8.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxy8num-1 {
|
||||
nextgalaxy8()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy8() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
@ -577,7 +581,10 @@ galaxy8 {
|
||||
planet8.species_kind = (planet8.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet8.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
planet8.goatsoup_seed[0] = lsb(seed[1])
|
||||
planet8.goatsoup_seed[1] = msb(seed[1])
|
||||
planet8.goatsoup_seed[2] = lsb(seed[2])
|
||||
planet8.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
@ -396,14 +396,18 @@ galaxy9 {
|
||||
sub init(ubyte galaxy9num) {
|
||||
number = 1
|
||||
planet9.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxy9num-1 {
|
||||
nextgalaxy9()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy9() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
@ -576,8 +580,10 @@ galaxy9 {
|
||||
planet9.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
|
||||
planet9.species_kind = (planet9.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet9.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
planet9.goatsoup_seed[0] = lsb(seed[1])
|
||||
planet9.goatsoup_seed[1] = msb(seed[1])
|
||||
planet9.goatsoup_seed[2] = lsb(seed[2])
|
||||
planet9.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
@ -406,14 +406,18 @@ galaxy {
|
||||
sub init(ubyte galaxynum) {
|
||||
number = 1
|
||||
planet.number = 255
|
||||
seed = [base0, base1, base2]
|
||||
seed[0] = base0
|
||||
seed[1] = base1
|
||||
seed[2] = base2
|
||||
repeat galaxynum-1 {
|
||||
nextgalaxy()
|
||||
}
|
||||
}
|
||||
|
||||
sub nextgalaxy() {
|
||||
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
|
||||
seed[0] = twist(seed[0])
|
||||
seed[1] = twist(seed[1])
|
||||
seed[2] = twist(seed[2])
|
||||
number++
|
||||
if number==9
|
||||
number = 1
|
||||
@ -587,7 +591,10 @@ galaxy {
|
||||
planet.species_kind = (planet.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
|
||||
}
|
||||
|
||||
planet.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
|
||||
planet.goatsoup_seed[0] = lsb(seed[1])
|
||||
planet.goatsoup_seed[1] = msb(seed[1])
|
||||
planet.goatsoup_seed[2] = lsb(seed[2])
|
||||
planet.goatsoup_seed[3] = seed2_msb
|
||||
}
|
||||
|
||||
sub tweakseed() {
|
@ -1,7 +1,7 @@
|
||||
plugins {
|
||||
id 'antlr'
|
||||
id 'java'
|
||||
id "org.jetbrains.kotlin.jvm" version "1.4.30"
|
||||
id "org.jetbrains.kotlin.jvm" version "1.4.32"
|
||||
}
|
||||
|
||||
targetCompatibility = 11
|
||||
@ -33,6 +33,7 @@ dependencies {
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
useIR = true
|
||||
// verbose = true
|
||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||
}
|
||||
@ -41,6 +42,7 @@ compileKotlin {
|
||||
compileTestKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
useIR = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,8 +121,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
output(datatypeString(decl.datatype))
|
||||
if(decl.arraysize!=null) {
|
||||
decl.arraysize!!.indexNum?.accept(this)
|
||||
decl.arraysize!!.indexVar?.accept(this)
|
||||
decl.arraysize!!.indexExpr.accept(this)
|
||||
}
|
||||
if(decl.isArray)
|
||||
output("]")
|
||||
@ -138,8 +137,11 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
output("\n")
|
||||
outputi("")
|
||||
if(subroutine.inline)
|
||||
output("inline ")
|
||||
if(subroutine.isAsmSubroutine) {
|
||||
outputi("asmsub ${subroutine.name} (")
|
||||
output("asmsub ${subroutine.name} (")
|
||||
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
|
||||
val reg =
|
||||
when {
|
||||
@ -153,7 +155,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
}
|
||||
else {
|
||||
outputi("sub ${subroutine.name} (")
|
||||
output("sub ${subroutine.name} (")
|
||||
for(param in subroutine.parameters) {
|
||||
output("${datatypeString(param.type)} ${param.name}")
|
||||
if(param!==subroutine.parameters.last())
|
||||
@ -365,8 +367,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
|
||||
arrayIndexedExpression.arrayvar.accept(this)
|
||||
output("[")
|
||||
arrayIndexedExpression.indexer.indexNum?.accept(this)
|
||||
arrayIndexedExpression.indexer.indexVar?.accept(this)
|
||||
arrayIndexedExpression.indexer.indexExpr.accept(this)
|
||||
output("]")
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,7 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.InferredTypes
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
@ -181,7 +178,6 @@ interface INameScope {
|
||||
}
|
||||
}
|
||||
|
||||
fun containsDefinedVariables() = statements.any { it is VarDecl && (it !is ParameterVarDecl) }
|
||||
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
||||
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
||||
|
||||
@ -244,10 +240,14 @@ interface IAssignable {
|
||||
// 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): NumericLiteralValue?
|
||||
fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue?
|
||||
fun returnType(name: String, args: MutableList<Expression>): InferredTypes.InferredType
|
||||
}
|
||||
|
||||
@ -255,23 +255,56 @@ interface IBuiltinFunctions {
|
||||
|
||||
|
||||
|
||||
class Program(val name: String, val modules: MutableList<Module>, val builtinFunctions: IBuiltinFunctions): Node {
|
||||
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
|
||||
get() = modules.first().loadAddress
|
||||
get() = mainModule.loadAddress
|
||||
|
||||
var actualLoadAddress: Int = 0
|
||||
private val internedStrings = mutableMapOf<Pair<String, Boolean>, List<String>>()
|
||||
val internedStringsModuleName = "prog8_interned_strings"
|
||||
|
||||
fun entrypoint(): Subroutine? {
|
||||
init {
|
||||
// insert a container module for all interned strings later
|
||||
if(modules.firstOrNull()?.name != internedStringsModuleName) {
|
||||
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, true, Path.of(""))
|
||||
modules.add(0, internedStringsModule)
|
||||
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
|
||||
internedStringsModule.statements.add(block)
|
||||
internedStringsModule.linkParents(this)
|
||||
internedStringsModule.program = this
|
||||
}
|
||||
}
|
||||
|
||||
fun entrypoint(): Subroutine {
|
||||
val mainBlocks = allBlocks().filter { it.name=="main" }
|
||||
if(mainBlocks.size > 1)
|
||||
throw FatalAstException("more than one 'main' block")
|
||||
return if(mainBlocks.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
mainBlocks[0].subScope("start") as Subroutine?
|
||||
}
|
||||
if(mainBlocks.isEmpty())
|
||||
throw FatalAstException("no 'main' block")
|
||||
return mainBlocks[0].subScope("start") as Subroutine
|
||||
}
|
||||
|
||||
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>() }
|
||||
@ -303,8 +336,6 @@ class Module(override val name: String,
|
||||
|
||||
override lateinit var parent: Node
|
||||
lateinit var program: Program
|
||||
val importedBy = mutableListOf<Module>()
|
||||
val imports = mutableSetOf<Module>()
|
||||
|
||||
val loadAddress: Int by lazy {
|
||||
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
|
||||
|
@ -60,7 +60,13 @@ enum class DataType {
|
||||
enum class CpuRegister {
|
||||
A,
|
||||
X,
|
||||
Y
|
||||
Y;
|
||||
|
||||
fun asRegisterOrPair(): RegisterOrPair = when(this) {
|
||||
A -> RegisterOrPair.A
|
||||
X -> RegisterOrPair.X
|
||||
Y -> RegisterOrPair.Y
|
||||
}
|
||||
}
|
||||
|
||||
enum class RegisterOrPair {
|
||||
|
@ -6,13 +6,15 @@ import prog8.ast.base.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import java.util.*
|
||||
import java.util.Objects
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||
val logicalOperators = setOf("and", "or", "xor", "not")
|
||||
|
||||
|
||||
sealed class Expression: Node {
|
||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||
@ -20,6 +22,7 @@ sealed class Expression: Node {
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
|
||||
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||
abstract val isSimple: Boolean
|
||||
|
||||
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
|
||||
|
||||
@ -103,6 +106,8 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
}
|
||||
}
|
||||
|
||||
override val isSimple = false
|
||||
|
||||
override fun toString(): String {
|
||||
return "Prefix($operator $expression)"
|
||||
}
|
||||
@ -131,6 +136,8 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
return "[$left $operator $right]"
|
||||
}
|
||||
|
||||
override val isSimple = false
|
||||
|
||||
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
|
||||
@ -240,6 +247,8 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
|
||||
indexer.linkParents(this)
|
||||
}
|
||||
|
||||
override val isSimple = indexer.indexExpr is NumericLiteralValue || indexer.indexExpr is IdentifierReference
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===arrayvar -> arrayvar = replacement as IdentifierReference
|
||||
@ -269,6 +278,8 @@ class ArrayIndexedExpression(var arrayvar: IdentifierReference,
|
||||
override fun toString(): String {
|
||||
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
|
||||
}
|
||||
|
||||
fun copy() = ArrayIndexedExpression(arrayvar.copy(), indexer.copy(), position)
|
||||
}
|
||||
|
||||
class TypecastExpression(var expression: Expression, var type: DataType, val implicit: Boolean, override val position: Position) : Expression() {
|
||||
@ -279,6 +290,8 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
expression.linkParents(this)
|
||||
}
|
||||
|
||||
override val isSimple = false
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===expression)
|
||||
expression = replacement
|
||||
@ -312,6 +325,8 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
||||
identifier.parent=this
|
||||
}
|
||||
|
||||
override val isSimple = true
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is IdentifierReference && node===identifier)
|
||||
identifier = replacement
|
||||
@ -333,6 +348,8 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
this.addressExpression.linkParents(this)
|
||||
}
|
||||
|
||||
override val isSimple = true
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===addressExpression)
|
||||
addressExpression = replacement
|
||||
@ -349,6 +366,8 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
override fun toString(): String {
|
||||
return "DirectMemoryRead($addressExpression)"
|
||||
}
|
||||
|
||||
fun copy() = DirectMemoryRead(addressExpression, position)
|
||||
}
|
||||
|
||||
class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
@ -356,6 +375,8 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override val isSimple = true
|
||||
|
||||
companion object {
|
||||
fun fromBoolean(bool: Boolean, position: Position) =
|
||||
NumericLiteralValue(DataType.UBYTE, if (bool) 1 else 0, position)
|
||||
@ -478,19 +499,17 @@ 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,
|
||||
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
val heapId = ++heapIdSequence
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override val isSimple = true
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
@ -516,13 +535,13 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
override val position: Position) : Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
val heapId = ++heapIdSequence
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
value.forEach {it.linkParents(this)}
|
||||
}
|
||||
|
||||
override val isSimple = true
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
val idx = value.indexOfFirst { it===node }
|
||||
@ -546,6 +565,14 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
return type==other.type && value.contentEquals(other.value)
|
||||
}
|
||||
|
||||
fun memsize(memsizer: IMemSizer): Int {
|
||||
if(type.isKnown) {
|
||||
val eltType = ArrayElementTypes.getValue(type.typeOrElse(DataType.STRUCT))
|
||||
return memsizer.memorySize(eltType) * value.size
|
||||
}
|
||||
else throw IllegalArgumentException("array datatype is not yet known")
|
||||
}
|
||||
|
||||
fun guessDatatype(program: Program): InferredTypes.InferredType {
|
||||
// Educated guess of the desired array literal's datatype.
|
||||
// If it's inside a for loop, assume the data type of the loop variable is what we want.
|
||||
@ -621,6 +648,8 @@ class RangeExpr(var from: Expression,
|
||||
step.linkParents(this)
|
||||
}
|
||||
|
||||
override val isSimple = true
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
when {
|
||||
@ -707,20 +736,21 @@ 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 val isSimple = true
|
||||
|
||||
fun targetStatement(program: Program) =
|
||||
if(nameInSource.size==1 && nameInSource[0] in program.builtinFunctions.names)
|
||||
BuiltinFunctionStatementPlaceholder(nameInSource[0], position)
|
||||
BuiltinFunctionStatementPlaceholder(nameInSource[0], position, parent)
|
||||
else
|
||||
program.namespace.lookup(nameInSource, this)
|
||||
|
||||
fun targetVarDecl(program: Program): VarDecl? = targetStatement(program) as? VarDecl
|
||||
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 // NOTE: only compare by the name, not the position!
|
||||
override fun hashCode() = nameInSource.hashCode()
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -763,17 +793,6 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
|
||||
fun memberOfStruct(program: Program) = this.targetVarDecl(program)?.struct
|
||||
|
||||
fun heapId(namespace: INameScope): Int {
|
||||
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(program: Program): String? {
|
||||
// take the name of the first struct member of the structvariable instead
|
||||
// if it's just a regular variable, return null.
|
||||
@ -801,6 +820,8 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
args.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override val isSimple = target.nameInSource.size==1 && (target.nameInSource[0] in setOf("msb", "lsb", "peek", "peekw"))
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
if(node===target)
|
||||
target=replacement as IdentifierReference
|
||||
@ -818,7 +839,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
// lenghts of arrays and strings are constants that are determined at compile time!
|
||||
if(target.nameInSource.size>1)
|
||||
return null
|
||||
val resultValue: NumericLiteralValue? = program.builtinFunctions.constValue(target.nameInSource[0], args, position)
|
||||
val resultValue: NumericLiteralValue? = program.builtinFunctions.constValue(target.nameInSource[0], args, position, program.memsizer)
|
||||
if(withDatatypeCheck) {
|
||||
val resultDt = this.inferType(program)
|
||||
if(resultValue==null || resultDt istype resultValue.type)
|
||||
|
@ -7,6 +7,10 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
|
||||
|
||||
interface ISymbolStatement {
|
||||
val name: String
|
||||
}
|
||||
|
||||
sealed class Statement : Node {
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
@ -31,8 +35,7 @@ sealed class Statement : Node {
|
||||
}
|
||||
|
||||
|
||||
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
|
||||
override var parent: Node = ParentSentinel
|
||||
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position, override var parent: Node) : Statement() {
|
||||
override fun linkParents(parent: Node) {}
|
||||
override fun accept(visitor: IAstVisitor) = throw FatalAstException("should not iterate over this node")
|
||||
override fun accept(visitor: AstWalker, parent: Node) = throw FatalAstException("should not iterate over this node")
|
||||
@ -48,7 +51,7 @@ class Block(override val name: String,
|
||||
val address: Int?,
|
||||
override var statements: MutableList<Statement>,
|
||||
val isInLibrary: Boolean,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
override val position: Position) : Statement(), INameScope, ISymbolStatement {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -95,7 +98,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
data class Label(val name: String, override val position: Position) : Statement() {
|
||||
data class Label(override val name: String, override val position: Position) : Statement(), ISymbolStatement {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -153,17 +156,16 @@ enum class ZeropageWish {
|
||||
NOT_IN_ZEROPAGE
|
||||
}
|
||||
|
||||
|
||||
open class VarDecl(val type: VarDeclType,
|
||||
private val declaredDatatype: DataType,
|
||||
val zeropage: ZeropageWish,
|
||||
var arraysize: ArrayIndex?,
|
||||
val name: String,
|
||||
override val name: String,
|
||||
private val structName: String?,
|
||||
var value: Expression?,
|
||||
val isArray: Boolean,
|
||||
val autogeneratedDontRemove: Boolean,
|
||||
override val position: Position) : Statement() {
|
||||
override val position: Position) : Statement(), ISymbolStatement {
|
||||
override lateinit var parent: Node
|
||||
var struct: StructDecl? = null // set later (because at parse time, we only know the name)
|
||||
private set
|
||||
@ -176,12 +178,6 @@ open class VarDecl(val type: VarDeclType,
|
||||
companion object {
|
||||
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 {
|
||||
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
||||
val arrayDt =
|
||||
@ -227,14 +223,18 @@ open class VarDecl(val type: VarDeclType,
|
||||
value?.linkParents(this)
|
||||
if(structName!=null) {
|
||||
val structStmt = definingScope().lookup(listOf(structName), this)
|
||||
if(structStmt!=null)
|
||||
struct = definingScope().lookup(listOf(structName), this) as StructDecl
|
||||
if(structStmt!=null) {
|
||||
val node = definingScope().lookup(listOf(structName), this)
|
||||
if(node is StructDecl)
|
||||
struct = node
|
||||
else
|
||||
datatypeErrors.add(SyntaxError("invalid datatype declaration", position))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
// TODO the check that node===value is too strict sometimes, but leaving it out allows for bugs to creep through ... :( Perhaps check when adding the replace if there is already a replace on the same node?
|
||||
require(replacement is Expression)
|
||||
require(replacement is Expression && (value==null || node===value))
|
||||
value = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
@ -256,7 +256,14 @@ open class VarDecl(val type: VarDeclType,
|
||||
fun flattenStructMembers(): MutableList<Statement> {
|
||||
val result = struct!!.statements.mapIndexed { index, statement ->
|
||||
val member = statement as VarDecl
|
||||
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[index] else null
|
||||
val initvalueOrig = if(value!=null) (value as ArrayLiteralValue).value[index] else null
|
||||
val initvalue = when(initvalueOrig) {
|
||||
is AddressOf -> initvalueOrig.copy()
|
||||
is ArrayIndexedExpression -> initvalueOrig.copy()
|
||||
is DirectMemoryRead -> initvalueOrig.copy()
|
||||
is IdentifierReference -> initvalueOrig.copy()
|
||||
else -> initvalueOrig
|
||||
}
|
||||
VarDecl(
|
||||
VarDeclType.VAR,
|
||||
member.datatype,
|
||||
@ -273,53 +280,32 @@ open class VarDecl(val type: VarDeclType,
|
||||
structHasBeenFlattened = true
|
||||
return result
|
||||
}
|
||||
|
||||
fun copy(): VarDecl {
|
||||
val c = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, value, isArray, autogeneratedDontRemove, position)
|
||||
c.allowInitializeWithZero = this.allowInitializeWithZero
|
||||
c.structHasBeenFlattened = this.structHasBeenFlattened
|
||||
return c
|
||||
}
|
||||
}
|
||||
|
||||
// a vardecl used only for subroutine parameters
|
||||
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
|
||||
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position)
|
||||
|
||||
|
||||
class ArrayIndex(var origExpression: Expression?, // will be replaced later by either the number or the identifier
|
||||
class ArrayIndex(var indexExpr: Expression,
|
||||
override val position: Position) : Node {
|
||||
// for code simplicity, either indexed via a constant number or via a variable (no arbitrary expressions)
|
||||
override lateinit var parent: Node
|
||||
var indexNum: NumericLiteralValue? = origExpression as? NumericLiteralValue
|
||||
var indexVar: IdentifierReference? = origExpression as? IdentifierReference
|
||||
|
||||
init {
|
||||
if(indexNum!=null || indexVar!=null)
|
||||
origExpression = null
|
||||
}
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
origExpression?.linkParents(this)
|
||||
indexNum?.linkParents(this)
|
||||
indexVar?.linkParents(this)
|
||||
indexExpr.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
when {
|
||||
node===origExpression -> origExpression = replacement
|
||||
node===indexVar -> {
|
||||
when (replacement) {
|
||||
is NumericLiteralValue -> {
|
||||
indexVar = null
|
||||
indexNum = replacement
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
indexVar = replacement
|
||||
indexNum = null
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("invalid replace")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
if (node===indexExpr) indexExpr = replacement
|
||||
else throw FatalAstException("invalid replace")
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -329,29 +315,17 @@ class ArrayIndex(var origExpression: Expression?, // will be replaced
|
||||
}
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) {
|
||||
origExpression?.accept(visitor)
|
||||
indexNum?.accept(visitor)
|
||||
indexVar?.accept(visitor)
|
||||
}
|
||||
fun accept(visitor: AstWalker, parent: Node) {
|
||||
origExpression?.accept(visitor, this)
|
||||
indexNum?.accept(visitor, this)
|
||||
indexVar?.accept(visitor, this)
|
||||
}
|
||||
fun accept(visitor: IAstVisitor) = indexExpr.accept(visitor)
|
||||
fun accept(visitor: AstWalker, parent: Node) = indexExpr.accept(visitor, this)
|
||||
|
||||
override fun toString(): String {
|
||||
return("ArrayIndex($indexNum, $indexVar, pos=$position)")
|
||||
return("ArrayIndex($indexExpr, pos=$position)")
|
||||
}
|
||||
|
||||
fun constIndex() = indexNum?.number?.toInt()
|
||||
fun constIndex() = (indexExpr as? NumericLiteralValue)?.number?.toInt()
|
||||
|
||||
infix fun isSameAs(other: ArrayIndex): Boolean {
|
||||
return if(indexNum!=null || indexVar!=null)
|
||||
indexNum==other.indexNum && indexVar == other.indexVar
|
||||
else
|
||||
other.origExpression!=null && origExpression!! isSameAs other.origExpression!!
|
||||
}
|
||||
infix fun isSameAs(other: ArrayIndex): Boolean = indexExpr isSameAs other.indexExpr
|
||||
fun copy() = ArrayIndex(indexExpr, position)
|
||||
}
|
||||
|
||||
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
|
||||
@ -469,9 +443,10 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
}
|
||||
|
||||
fun toExpression(): Expression {
|
||||
// return a copy of the assignment target but as a source expression.
|
||||
return when {
|
||||
identifier != null -> identifier!!
|
||||
arrayindexed != null -> arrayindexed!!
|
||||
identifier != null -> identifier!!.copy()
|
||||
arrayindexed != null -> arrayindexed!!.copy()
|
||||
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
|
||||
else -> throw FatalAstException("invalid assignmenttarget $this")
|
||||
}
|
||||
@ -516,8 +491,9 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun copy() = AssignTarget(identifier?.copy(), arrayindexed?.copy(), memoryAddress?.copy(), position)
|
||||
}
|
||||
|
||||
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
@ -645,20 +621,16 @@ class NopStatement(override val position: Position): Statement() {
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
|
||||
class AsmGenInfo {
|
||||
// This class contains various attributes that influence the assembly code generator.
|
||||
// Conceptually it should be part of any INameScope.
|
||||
// But because the resulting code only creates "real" scopes on a subroutine level,
|
||||
// it's more consistent to only define these attributes on a Subroutine node.
|
||||
var usedAutoArrayIndexerForStatements = mutableListOf<ArrayIndexerInfo>()
|
||||
var usedRegsaveA = false
|
||||
var usedRegsaveX = false
|
||||
var usedRegsaveY = false
|
||||
var usedFloatEvalResultVar1 = false
|
||||
var usedFloatEvalResultVar2 = false
|
||||
|
||||
class ArrayIndexerInfo(val name: String, val replaces: ArrayIndex)
|
||||
}
|
||||
|
||||
// the subroutine class covers both the normal user-defined subroutines,
|
||||
@ -674,7 +646,7 @@ class Subroutine(override val name: String,
|
||||
val isAsmSubroutine: Boolean,
|
||||
val inline: Boolean,
|
||||
override var statements: MutableList<Statement>,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
override val position: Position) : Statement(), INameScope, ISymbolStatement {
|
||||
|
||||
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, inline: Boolean, position: Position)
|
||||
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, inline, statements, position)
|
||||
@ -734,10 +706,9 @@ class Subroutine(override val name: String,
|
||||
.asSequence()
|
||||
.filter { it is InlineAssembly }
|
||||
.map { (it as InlineAssembly).assembly }
|
||||
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
|
||||
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it || " bra" in it || "\tbra" in it}
|
||||
}
|
||||
|
||||
|
||||
open class SubroutineParameter(val name: String,
|
||||
val type: DataType,
|
||||
override val position: Position) : Node {
|
||||
@ -984,7 +955,6 @@ class WhenChoice(var values: MutableList<Expression>?, // if null, th
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
|
||||
class StructDecl(override val name: String,
|
||||
override var statements: MutableList<Statement>, // actually, only vardecls here
|
||||
override val position: Position): Statement(), INameScope {
|
||||
@ -1006,6 +976,9 @@ class StructDecl(override val name: String,
|
||||
val numberOfElements: Int
|
||||
get() = this.statements.size
|
||||
|
||||
fun memsize(memsizer: IMemSizer) =
|
||||
statements.map { memsizer.memorySize((it as VarDecl).datatype) }.sum()
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -1032,4 +1005,5 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
|
||||
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
fun copy() = DirectMemoryWrite(addressExpression, position)
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ interface IAstModification {
|
||||
}
|
||||
}
|
||||
|
||||
class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) :
|
||||
class ReplaceNode(val node: Node, private val replacement: Node, private val parent: Node) :
|
||||
IAstModification {
|
||||
override fun perform() {
|
||||
parent.replaceChildNode(node, replacement)
|
||||
@ -76,94 +76,117 @@ interface IAstModification {
|
||||
|
||||
|
||||
abstract class AstWalker {
|
||||
open fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(assignment: Assignment, 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(breakStmt: Break, 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(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(label: Label, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(module: Module, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
protected val noModifications = emptyList<IAstModification>()
|
||||
|
||||
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(block: Block, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(label: Label, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(module: Module, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(block: Block, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(label: Label, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(module: Module, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(program: Program, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
|
||||
private val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
|
||||
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(block: Block, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(label: Label, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(module: Module, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(program: Program, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = noModifications
|
||||
|
||||
protected val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
|
||||
|
||||
private fun track(mods: Iterable<IAstModification>, node: Node, parent: Node) {
|
||||
for (it in mods) modifications += Triple(it, node, parent)
|
||||
for (it in mods) {
|
||||
// if(it is IAstModification.ReplaceNode) {
|
||||
// val replaceKey = Pair(it.node, it.node.position)
|
||||
// if(replaceKey in modificationsReplacedNodes)
|
||||
// throw FatalAstException("there already is a node replacement for $replaceKey - optimizer can't deal with multiple replacements for same node yet. Split the ast modification?")
|
||||
// else
|
||||
// modificationsReplacedNodes.add(replaceKey)
|
||||
// }
|
||||
modifications += Triple(it, node, parent)
|
||||
}
|
||||
}
|
||||
|
||||
fun applyModifications(): Int {
|
||||
// check if there are double removes, keep only the last one
|
||||
val removals = modifications.filter { it.first is IAstModification.Remove }
|
||||
if(removals.size>0) {
|
||||
val doubles = removals.groupBy { (it.first as IAstModification.Remove).node }.filter { it.value.size>1 }
|
||||
doubles.forEach {
|
||||
for(doubleRemove in it.value.dropLast(1)) {
|
||||
if(!modifications.removeIf { mod-> mod.first === doubleRemove.first })
|
||||
throw FatalAstException("ast remove problem")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modifications.forEach {
|
||||
it.first.perform()
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ class ModuleImporter {
|
||||
if(!Files.isReadable(filePath))
|
||||
throw ParsingFailedError("No such file: $filePath")
|
||||
|
||||
val input = CharStreams.fromPath(filePath)
|
||||
return importModule(program, input, filePath, false, encoder, compilationTargetName)
|
||||
val content = filePath.toFile().readText().replace("\r\n", "\n")
|
||||
return importModule(program, CharStreams.fromString(content), filePath, false, encoder, compilationTargetName)
|
||||
}
|
||||
|
||||
fun importLibraryModule(program: Program, name: String,
|
||||
@ -62,6 +62,8 @@ class ModuleImporter {
|
||||
is prog8Parser -> System.err.println("${recognizer.inputStream.sourceName}:$line:$charPositionInLine: $msg")
|
||||
else -> System.err.println("$line:$charPositionInLine $msg")
|
||||
}
|
||||
if(numberOfErrors>=5)
|
||||
throw ParsingFailedError("There are too many parse errors. Stopping.")
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +122,8 @@ class ModuleImporter {
|
||||
val (resource, resourcePath) = rsc
|
||||
resource.use {
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"),
|
||||
val content = it.reader().readText().replace("\r\n", "\n")
|
||||
importModule(program, CharStreams.fromString(content), Paths.get("@embedded@/$resourcePath"),
|
||||
true, encoder, compilationTargetName)
|
||||
}
|
||||
} else {
|
||||
|
@ -2,7 +2,7 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm" version "1.4.30"
|
||||
id "org.jetbrains.kotlin.jvm" version "1.4.32"
|
||||
id 'com.github.johnrengelman.shadow' version '6.1.0'
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ repositories {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.2'
|
||||
implementation "com.github.hypfvieh:dbus-java:3.2.4"
|
||||
implementation "org.slf4j:slf4j-simple:1.7.30"
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</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" />
|
||||
</component>
|
||||
</module>
|
@ -20,7 +20,7 @@ import os
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = 'Prog8'
|
||||
copyright = '2019, Irmen de Jong'
|
||||
copyright = '2021, Irmen de Jong'
|
||||
author = 'Irmen de Jong'
|
||||
|
||||
|
||||
|
@ -42,22 +42,25 @@ Language features
|
||||
-----------------
|
||||
|
||||
- It is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...)
|
||||
The generated output is a machine code program runnable on actual 8-bit 6502 hardware.
|
||||
- Provide a very convenient edit/compile/run cycle by being able to directly launch
|
||||
It generates a machine code program runnable on actual 8-bit 6502 hardware.
|
||||
- Fast execution speed due to compilation to native assembly code. It's possible to write certain raster interrupt 'demoscene' effects purely in Prog8.
|
||||
- Provides a very convenient edit/compile/run cycle by being able to directly launch
|
||||
the compiled program in an emulator and provide debugging information to this emulator.
|
||||
- Based on simple and familiar imperative structured programming (it looks like a mix of C and Python)
|
||||
- Modular programming and scoping via modules, code blocks, and subroutines.
|
||||
- Provide high level programming constructs but at the same time stay close to the metal;
|
||||
still able to directly use memory addresses and ROM subroutines,
|
||||
and inline assembly to have full control when every register, cycle or byte matters
|
||||
- Arbitrary number of subroutine parameters, Complex nested expressions are possible
|
||||
- No stack frame allocations because parameters and local variables are automatically allocated statically
|
||||
- Subroutines with parameters and return values
|
||||
- complex nested expressions are possible
|
||||
- Variables are allocated statically
|
||||
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
|
||||
- Variable data types include signed and unsigned bytes and words, arrays, strings and floats.
|
||||
- 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``
|
||||
- Programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- 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
|
||||
@ -145,6 +148,8 @@ For MacOS you can use the Homebrew system to install a recent version of OpenJDK
|
||||
Finally: an **emulator** (or a real machine ofcourse) to test and run your programs on.
|
||||
In C64 mode, thhe compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
If you're targeting the CommanderX16 instead, there's the `x16emu <https://github.com/commanderx16/x16-emulator>`_.
|
||||
Make sure you use cx16 emulator and roms **V39 or newer**! Starting from version 6.5, prog8 targets that system version.
|
||||
Your program may work on V38 but that will only be by luck.
|
||||
|
||||
.. important::
|
||||
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
|
||||
@ -161,11 +166,11 @@ If you're targeting the CommanderX16 instead, there's the `x16emu <https://githu
|
||||
:maxdepth: 2
|
||||
:caption: Contents of this manual:
|
||||
|
||||
targetsystem.rst
|
||||
building.rst
|
||||
programming.rst
|
||||
syntaxreference.rst
|
||||
libraries.rst
|
||||
targetsystem.rst
|
||||
technical.rst
|
||||
todo.rst
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user