mirror of
https://github.com/irmen/prog8.git
synced 2025-06-17 16:23:42 +00:00
Compare commits
179 Commits
Author | SHA1 | Date | |
---|---|---|---|
b37231d0f5 | |||
3c55719bf1 | |||
af8279a9b9 | |||
c38508c262 | |||
b0e8738ab8 | |||
cae480768e | |||
a70276c190 | |||
0c461ffe2e | |||
237511f2d6 | |||
cdcb652033 | |||
71e678b382 | |||
3050156325 | |||
4bfdbad2e4 | |||
06137ecdc4 | |||
d89f5b0df8 | |||
b6e2b36692 | |||
a6d789cfbc | |||
c07907e7bd | |||
7d8496c874 | |||
164ac56db1 | |||
fdddb8ca64 | |||
a9d4b8b0fa | |||
ec7b9f54c2 | |||
307558a7e7 | |||
febf423eab | |||
a999c23014 | |||
69f1ade595 | |||
b166576e54 | |||
ee2ba5f398 | |||
cb9825484d | |||
76cda82e23 | |||
37b61d9e6b | |||
52f0222a6d | |||
75ccac2f2c | |||
5c771a91f7 | |||
a242ad10e6 | |||
b5086b6a8f | |||
3e47dad12a | |||
235610f40c | |||
6b59559c65 | |||
23e954f716 | |||
983c899cad | |||
c2f9385965 | |||
ceb2c9e4f8 | |||
68a7f9c665 | |||
ffd8d9c7c1 | |||
c66fc8630c | |||
9ca1c66f2b | |||
33647a29d0 | |||
02b12cc762 | |||
3280993e2a | |||
3723c22054 | |||
0a2c4ea0c4 | |||
58a83c0439 | |||
d665489054 | |||
9200992024 | |||
6408cc46a8 | |||
961bcdb7ae | |||
edee70cf31 | |||
1978a9815a | |||
f5e6db9d66 | |||
a94bc40ab0 | |||
534b5ced8f | |||
5ebd9b54e4 | |||
cc4e272526 | |||
295e199bfa | |||
df3371b0f0 | |||
e4fe1d2b8d | |||
b8b9244ffa | |||
3be3989e1c | |||
ed54cf680a | |||
95e76058d3 | |||
a6bee6a860 | |||
d22780ee44 | |||
f8b0b9575d | |||
4274fd168e | |||
be7f5957f3 | |||
f2e5d987a9 | |||
f01173d8db | |||
15e8e0bf6d | |||
2c59cbdece | |||
b73da4ed02 | |||
267adb4612 | |||
05c73fa8bc | |||
bfe9f442e6 | |||
0deadb694b | |||
bed34378be | |||
5927cf2d43 | |||
fffe36e358 | |||
fac2a2d7cb | |||
0af5582ca7 | |||
582d31263c | |||
4108a528e1 | |||
ab7d7c2907 | |||
152888ee93 | |||
22f8f4f359 | |||
5f3a9e189a | |||
b734dc44fd | |||
fab224f509 | |||
2f05ebb966 | |||
a335ba519a | |||
8805693ed2 | |||
f2bb238e9b | |||
131fe670a4 | |||
11e9539416 | |||
3881ebe429 | |||
29d1b8802e | |||
bcc75732e9 | |||
50a85ee6b0 | |||
2c7424fd43 | |||
7426587c38 | |||
1f39749a5e | |||
ca63051c71 | |||
6dd44aaf0d | |||
f89457ba68 | |||
efef205fcf | |||
0c561d8528 | |||
8bfa2c4c02 | |||
f0d4c3aba9 | |||
3a99115070 | |||
7232134931 | |||
954e911eb3 | |||
63c073c93f | |||
78feef9d59 | |||
4fbdd6d570 | |||
4929c198ba | |||
9409f17372 | |||
43781c02d0 | |||
824f06e17f | |||
21dbc6da97 | |||
270ea54ff7 | |||
771ac7aba7 | |||
97d36243f2 | |||
511b47bac4 | |||
f265199fbe | |||
a191ec71a4 | |||
82dce2dd53 | |||
29ac160811 | |||
5e50ea14f8 | |||
40e6091506 | |||
0ee4d420b1 | |||
66acce9e8e | |||
6c23ae14ab | |||
6f000d0d26 | |||
9d7eb3be5a | |||
835555171e | |||
68ce4a1bf0 | |||
a995867deb | |||
6bd99d63b4 | |||
baf5d3041a | |||
a326ffa00a | |||
d28dd92b47 | |||
1de328b2e8 | |||
51bb902162 | |||
4fd14f1366 | |||
91d9559f79 | |||
3245a9b157 | |||
2b28493bba | |||
1382728bd2 | |||
0422ad080a | |||
64d682bfde | |||
b182f7e693 | |||
e6be428589 | |||
85c7f8314b | |||
796d07a7f8 | |||
2af86a10b2 | |||
7fbe486dff | |||
87e5a9859a | |||
b036e5ed72 | |||
5f1ec80ae0 | |||
fbecedaf41 | |||
aa36acd65a | |||
8d1a4588d3 | |||
66d2af4453 | |||
ef6c731bb3 | |||
98a638a2f3 | |||
96d8a7f0d7 | |||
3162b10392 | |||
e2358de27c |
13
.gitignore
vendored
13
.gitignore
vendored
@ -1,8 +1,8 @@
|
|||||||
.idea/workspace.xml
|
.idea/workspace.xml
|
||||||
.idea/discord.xml
|
.idea/discord.xml
|
||||||
/build/
|
build/
|
||||||
/dist/
|
dist/
|
||||||
/output/
|
output/
|
||||||
.*cache/
|
.*cache/
|
||||||
*.directory
|
*.directory
|
||||||
*.prg
|
*.prg
|
||||||
@ -12,9 +12,9 @@
|
|||||||
*.vice-mon-list
|
*.vice-mon-list
|
||||||
docs/build
|
docs/build
|
||||||
out/
|
out/
|
||||||
**/*.interp
|
parser/**/*.interp
|
||||||
**/*.tokens
|
parser/**/*.tokens
|
||||||
|
parser/**/*.java
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*.egg
|
*.egg
|
||||||
*.egg-info
|
*.egg-info
|
||||||
@ -28,5 +28,4 @@ parsetab.py
|
|||||||
.attach_pid*
|
.attach_pid*
|
||||||
|
|
||||||
.gradle
|
.gradle
|
||||||
build/
|
|
||||||
/prog8compiler.jar
|
/prog8compiler.jar
|
||||||
|
15
README.md
15
README.md
@ -1,5 +1,6 @@
|
|||||||
[](https://saythanks.io/to/irmen)
|
[](https://saythanks.io/to/irmen)
|
||||||
[](https://travis-ci.org/irmen/prog8)
|
[](https://travis-ci.org/irmen/prog8)
|
||||||
|
[](https://prog8.readthedocs.io/)
|
||||||
|
|
||||||
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
|
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
|
||||||
===========================================================================
|
===========================================================================
|
||||||
@ -17,7 +18,7 @@ which aims to provide many conveniences over raw assembly code (even when using
|
|||||||
- modularity, symbol scoping, subroutines
|
- modularity, symbol scoping, subroutines
|
||||||
- various data types other than just bytes (16-bit words, floats, strings)
|
- various data types other than just bytes (16-bit words, floats, strings)
|
||||||
- automatic variable allocations, automatic string and array variables and string sharing
|
- automatic variable allocations, automatic string and array variables and string sharing
|
||||||
- subroutines with a input- and output parameter signature
|
- subroutines with an input- and output parameter signature
|
||||||
- constant folding in expressions
|
- constant folding in expressions
|
||||||
- conditional branches
|
- conditional branches
|
||||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||||
@ -30,20 +31,18 @@ which aims to provide many conveniences over raw assembly code (even when using
|
|||||||
|
|
||||||
Rapid edit-compile-run-debug cycle:
|
Rapid edit-compile-run-debug cycle:
|
||||||
|
|
||||||
- use modern PC to work on
|
- use a modern PC to do the work on
|
||||||
- quick compilation times (seconds)
|
- very quick compilation times
|
||||||
- option to automatically run the program in the Vice emulator
|
- can automatically run the program in the Vice emulator after succesful compilation
|
||||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||||
- virtual machine that can execute compiled code directy on the host system,
|
|
||||||
without having to actually convert it to assembly to run on a real 6502
|
|
||||||
|
|
||||||
It is mainly targeted at the Commodore-64 machine at this time.
|
Prog8 is mainly targeted at the Commodore-64 machine at this time.
|
||||||
Contributions to add support for other 8-bit (or other?!) machines are welcome.
|
Contributions to add support for other 8-bit (or other?!) machines are welcome.
|
||||||
|
|
||||||
Documentation/manual
|
Documentation/manual
|
||||||
--------------------
|
--------------------
|
||||||
This describes the language, but also how to build and run the compiler. See https://prog8.readthedocs.io/
|
https://prog8.readthedocs.io/
|
||||||
|
|
||||||
Required tools
|
Required tools
|
||||||
--------------
|
--------------
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
// id "org.jetbrains.kotlin.jvm" version "1.3.61"
|
// id "org.jetbrains.kotlin.jvm" version "1.3.72"
|
||||||
id 'application'
|
id 'application'
|
||||||
id 'org.jetbrains.dokka' version "0.9.18"
|
id 'org.jetbrains.dokka' version "0.9.18"
|
||||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||||
@ -34,6 +34,7 @@ dependencies {
|
|||||||
implementation 'org.antlr:antlr4-runtime:4.8'
|
implementation 'org.antlr:antlr4-runtime:4.8'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
|
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
|
||||||
// implementation 'net.razorvine:ksim65:1.6'
|
// implementation 'net.razorvine:ksim65:1.6'
|
||||||
|
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
|
||||||
implementation project(':parser')
|
implementation project(':parser')
|
||||||
|
|
||||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
|
||||||
@ -100,7 +101,7 @@ test {
|
|||||||
|
|
||||||
// Show test results.
|
// Show test results.
|
||||||
testLogging {
|
testLogging {
|
||||||
events "passed", "skipped", "failed"
|
events "skipped", "failed"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
compiler/lib/dbus-java-3.2.0.jar
Normal file
BIN
compiler/lib/dbus-java-3.2.0.jar
Normal file
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.0 KiB |
798
compiler/res/prog8lib/c64floats.asm
Normal file
798
compiler/res/prog8lib/c64floats.asm
Normal file
@ -0,0 +1,798 @@
|
|||||||
|
; --- low level floating point assembly routines for the C64
|
||||||
|
|
||||||
|
ub2float .proc
|
||||||
|
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
||||||
|
; clobbers A, Y
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
sta c64.SCRATCH_ZPWORD2
|
||||||
|
sty c64.SCRATCH_ZPWORD2+1
|
||||||
|
ldy c64.SCRATCH_ZPB1
|
||||||
|
jsr FREADUY
|
||||||
|
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
|
||||||
|
ldy c64.SCRATCH_ZPWORD2+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx c64.SCRATCH_ZPREGX
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
b2float .proc
|
||||||
|
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
||||||
|
; clobbers A, Y
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
sta c64.SCRATCH_ZPWORD2
|
||||||
|
sty c64.SCRATCH_ZPWORD2+1
|
||||||
|
lda c64.SCRATCH_ZPB1
|
||||||
|
jsr FREADSA
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
uw2float .proc
|
||||||
|
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
sta c64.SCRATCH_ZPWORD2
|
||||||
|
sty c64.SCRATCH_ZPWORD2+1
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
ldy c64.SCRATCH_ZPWORD1+1
|
||||||
|
jsr GIVUAYFAY
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
w2float .proc
|
||||||
|
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
sta c64.SCRATCH_ZPWORD2
|
||||||
|
sty c64.SCRATCH_ZPWORD2+1
|
||||||
|
ldy c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.SCRATCH_ZPWORD1+1
|
||||||
|
jsr GIVAYF
|
||||||
|
jmp ub2float._fac_to_mem
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_b2float .proc
|
||||||
|
; -- b2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr FREADSA
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_w2float .proc
|
||||||
|
; -- w2float operating on the stack
|
||||||
|
inx
|
||||||
|
ldy c64.ESTACK_LO,x
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr GIVAYF
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_ub2float .proc
|
||||||
|
; -- ub2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
tay
|
||||||
|
jsr FREADUY
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_uw2float .proc
|
||||||
|
; -- uw2float operating on the stack
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
ldy c64.ESTACK_HI,x
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr GIVUAYFAY
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_float2w .proc ; also used for float2b
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr AYINT
|
||||||
|
ldx c64.SCRATCH_ZPREGX
|
||||||
|
lda $64
|
||||||
|
sta c64.ESTACK_HI,x
|
||||||
|
lda $65
|
||||||
|
sta c64.ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
stack_float2uw .proc ; also used for float2ub
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr GETADR
|
||||||
|
ldx c64.SCRATCH_ZPREGX
|
||||||
|
sta c64.ESTACK_HI,x
|
||||||
|
tya
|
||||||
|
sta c64.ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
push_float .proc
|
||||||
|
; ---- push mflpt5 in A/Y onto stack
|
||||||
|
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
sty c64.SCRATCH_ZPWORD1+1
|
||||||
|
ldy #0
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta c64.ESTACK_LO,x
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta c64.ESTACK_HI,x
|
||||||
|
dex
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta c64.ESTACK_LO,x
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta c64.ESTACK_HI,x
|
||||||
|
dex
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta c64.ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rndf .proc
|
||||||
|
; -- put a random floating point value on the stack
|
||||||
|
stx c64.SCRATCH_ZPREG
|
||||||
|
lda #1
|
||||||
|
jsr FREADSA
|
||||||
|
jsr RND ; rng into fac1
|
||||||
|
ldx #<_rndf_rnum5
|
||||||
|
ldy #>_rndf_rnum5
|
||||||
|
jsr MOVMF ; fac1 to mem X/Y
|
||||||
|
ldx c64.SCRATCH_ZPREG
|
||||||
|
lda #<_rndf_rnum5
|
||||||
|
ldy #>_rndf_rnum5
|
||||||
|
jmp push_float
|
||||||
|
_rndf_rnum5 .byte 0,0,0,0,0
|
||||||
|
.pend
|
||||||
|
|
||||||
|
push_float_from_indexed_var .proc
|
||||||
|
; -- push the float from the array at A/Y with index on stack, onto the stack.
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
sty c64.SCRATCH_ZPWORD1+1
|
||||||
|
jsr prog8_lib.pop_index_times_5
|
||||||
|
jsr prog8_lib.add_a_to_zpword
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
ldy c64.SCRATCH_ZPWORD1+1
|
||||||
|
jmp push_float
|
||||||
|
.pend
|
||||||
|
|
||||||
|
pop_float .proc
|
||||||
|
; ---- pops mflpt5 from stack to memory A/Y
|
||||||
|
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
sty c64.SCRATCH_ZPWORD1+1
|
||||||
|
ldy #4
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
pop_float_fac1 .proc
|
||||||
|
; -- pops float from stack into FAC1
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jmp MOVFM
|
||||||
|
.pend
|
||||||
|
|
||||||
|
pop_float_to_indexed_var .proc
|
||||||
|
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
sty c64.SCRATCH_ZPWORD1+1
|
||||||
|
jsr prog8_lib.pop_index_times_5
|
||||||
|
jsr prog8_lib.add_a_to_zpword
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
ldy c64.SCRATCH_ZPWORD1+1
|
||||||
|
jmp pop_float
|
||||||
|
.pend
|
||||||
|
|
||||||
|
copy_float .proc
|
||||||
|
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||||
|
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||||
|
sta c64.SCRATCH_ZPWORD2
|
||||||
|
sty c64.SCRATCH_ZPWORD2+1
|
||||||
|
ldy #0
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
inc_var_f .proc
|
||||||
|
; -- add 1 to float pointed to by A/Y
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
sty c64.SCRATCH_ZPWORD1+1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr MOVFM
|
||||||
|
lda #<FL_FONE
|
||||||
|
ldy #>FL_FONE
|
||||||
|
jsr FADD
|
||||||
|
ldx c64.SCRATCH_ZPWORD1
|
||||||
|
ldy c64.SCRATCH_ZPWORD1+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx c64.SCRATCH_ZPREGX
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
dec_var_f .proc
|
||||||
|
; -- subtract 1 from float pointed to by A/Y
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
sty c64.SCRATCH_ZPWORD1+1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
lda #<FL_FONE
|
||||||
|
ldy #>FL_FONE
|
||||||
|
jsr MOVFM
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
ldy c64.SCRATCH_ZPWORD1+1
|
||||||
|
jsr FSUB
|
||||||
|
ldx c64.SCRATCH_ZPWORD1
|
||||||
|
ldy c64.SCRATCH_ZPWORD1+1
|
||||||
|
jsr MOVMF
|
||||||
|
ldx c64.SCRATCH_ZPREGX
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
inc_indexed_var_f .proc
|
||||||
|
; -- add 1 to float in array pointed to by A/Y, at index X
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc c64.SCRATCH_ZPB1
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
adc c64.SCRATCH_ZPB1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jmp inc_var_f
|
||||||
|
.pend
|
||||||
|
|
||||||
|
dec_indexed_var_f .proc
|
||||||
|
; -- subtract 1 to float in array pointed to by A/Y, at index X
|
||||||
|
pha
|
||||||
|
txa
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc c64.SCRATCH_ZPB1
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
pla
|
||||||
|
clc
|
||||||
|
adc c64.SCRATCH_ZPB1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jmp dec_var_f
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
pop_2_floats_f2_in_fac1 .proc
|
||||||
|
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jmp MOVFM
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
||||||
|
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
||||||
|
|
||||||
|
push_fac1_as_result .proc
|
||||||
|
; -- push the float in FAC1 onto the stack, and return from calculation
|
||||||
|
ldx #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr MOVMF
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
ldx c64.SCRATCH_ZPREGX
|
||||||
|
jmp push_float
|
||||||
|
.pend
|
||||||
|
|
||||||
|
pow_f .proc
|
||||||
|
; -- push f1 ** f2 on stack
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr CONUPK ; fac2 = float1
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr FPWR
|
||||||
|
ldx c64.SCRATCH_ZPREGX
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
div_f .proc
|
||||||
|
; -- push f1/f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FDIV
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
add_f .proc
|
||||||
|
; -- push f1+f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FADD
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
sub_f .proc
|
||||||
|
; -- push f1-f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FSUB
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
mul_f .proc
|
||||||
|
; -- push f1*f2 on stack
|
||||||
|
jsr pop_2_floats_f2_in_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FMULT
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
neg_f .proc
|
||||||
|
; -- push -flt back on stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr NEGOP
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
abs_f .proc
|
||||||
|
; -- push abs(float) on stack (as float)
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr ABS
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
equal_f .proc
|
||||||
|
; -- are the two mflpt5 numbers on the stack identical?
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO-3,x
|
||||||
|
cmp c64.ESTACK_LO,x
|
||||||
|
bne _equals_false
|
||||||
|
lda c64.ESTACK_LO-2,x
|
||||||
|
cmp c64.ESTACK_LO+1,x
|
||||||
|
bne _equals_false
|
||||||
|
lda c64.ESTACK_LO-1,x
|
||||||
|
cmp c64.ESTACK_LO+2,x
|
||||||
|
bne _equals_false
|
||||||
|
lda c64.ESTACK_HI-2,x
|
||||||
|
cmp c64.ESTACK_HI+1,x
|
||||||
|
bne _equals_false
|
||||||
|
lda c64.ESTACK_HI-1,x
|
||||||
|
cmp c64.ESTACK_HI+2,x
|
||||||
|
bne _equals_false
|
||||||
|
_equals_true lda #1
|
||||||
|
_equals_store inx
|
||||||
|
sta c64.ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
_equals_false lda #0
|
||||||
|
beq _equals_store
|
||||||
|
.pend
|
||||||
|
|
||||||
|
notequal_f .proc
|
||||||
|
; -- are the two mflpt5 numbers on the stack different?
|
||||||
|
jsr equal_f
|
||||||
|
eor #1 ; invert the result
|
||||||
|
sta c64.ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
less_f .proc
|
||||||
|
; -- is f1 < f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #255
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
lesseq_f .proc
|
||||||
|
; -- is f1 <= f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #255
|
||||||
|
beq compare_floats._return_true
|
||||||
|
cmp #0
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greater_f .proc
|
||||||
|
; -- is f1 > f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #1
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
greatereq_f .proc
|
||||||
|
; -- is f1 >= f2?
|
||||||
|
jsr compare_floats
|
||||||
|
cmp #1
|
||||||
|
beq compare_floats._return_true
|
||||||
|
cmp #0
|
||||||
|
beq compare_floats._return_true
|
||||||
|
bne compare_floats._return_false
|
||||||
|
.pend
|
||||||
|
|
||||||
|
compare_floats .proc
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr pop_float
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr MOVFM ; fac1 = flt1
|
||||||
|
lda #<fmath_float2
|
||||||
|
ldy #>fmath_float2
|
||||||
|
stx c64.SCRATCH_ZPREG
|
||||||
|
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
||||||
|
ldx c64.SCRATCH_ZPREG
|
||||||
|
rts
|
||||||
|
_return_false lda #0
|
||||||
|
_return_result sta c64.ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
_return_true lda #1
|
||||||
|
bne _return_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sin .proc
|
||||||
|
; -- push sin(f) back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr SIN
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_cos .proc
|
||||||
|
; -- push cos(f) back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr COS
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_tan .proc
|
||||||
|
; -- push tan(f) back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr TAN
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_atan .proc
|
||||||
|
; -- push atan(f) back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr ATN
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ln .proc
|
||||||
|
; -- push ln(f) back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr LOG
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_log2 .proc
|
||||||
|
; -- push log base 2, ln(f)/ln(2), back onto stack
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr LOG
|
||||||
|
jsr MOVEF
|
||||||
|
lda #<c64.FL_LOG2
|
||||||
|
ldy #>c64.FL_LOG2
|
||||||
|
jsr MOVFM
|
||||||
|
jsr FDIVT
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sqrt .proc
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr SQR
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rad .proc
|
||||||
|
; -- convert degrees to radians (d * pi / 180)
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
lda #<_pi_div_180
|
||||||
|
ldy #>_pi_div_180
|
||||||
|
jsr FMULT
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_deg .proc
|
||||||
|
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
lda #<_one_over_pi_div_180
|
||||||
|
ldy #>_one_over_pi_div_180
|
||||||
|
jsr FMULT
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_round .proc
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr FADDH
|
||||||
|
jsr INT
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_floor .proc
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
jsr INT
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_ceil .proc
|
||||||
|
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||||
|
jsr pop_float_fac1
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
ldx #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr MOVMF
|
||||||
|
jsr INT
|
||||||
|
lda #<fmath_float1
|
||||||
|
ldy #>fmath_float1
|
||||||
|
jsr FCOMP
|
||||||
|
cmp #0
|
||||||
|
beq +
|
||||||
|
lda #<FL_FONE
|
||||||
|
ldy #>FL_FONE
|
||||||
|
jsr FADD
|
||||||
|
+ jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_any_f .proc
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x ; array size
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
||||||
|
jmp prog8_lib.func_any_b._entry
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_all_f .proc
|
||||||
|
inx
|
||||||
|
jsr prog8_lib.peek_address
|
||||||
|
lda c64.ESTACK_LO,x ; array size
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
||||||
|
tay
|
||||||
|
dey
|
||||||
|
- lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
clc
|
||||||
|
dey
|
||||||
|
adc (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
adc (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
adc (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
adc (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
cmp #0
|
||||||
|
beq +
|
||||||
|
cpy #255
|
||||||
|
bne -
|
||||||
|
lda #1
|
||||||
|
sta c64.ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
+ sta c64.ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_max_f .proc
|
||||||
|
lda #255
|
||||||
|
sta _minmax_cmp+1
|
||||||
|
lda #<_largest_neg_float
|
||||||
|
ldy #>_largest_neg_float
|
||||||
|
_minmax_entry jsr MOVFM
|
||||||
|
jsr prog8_lib.pop_array_and_lengthmin1Y
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
- sty c64.SCRATCH_ZPREG
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
ldy c64.SCRATCH_ZPWORD1+1
|
||||||
|
jsr FCOMP
|
||||||
|
_minmax_cmp cmp #255 ; modified
|
||||||
|
bne +
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
ldy c64.SCRATCH_ZPWORD1+1
|
||||||
|
jsr MOVFM
|
||||||
|
+ lda c64.SCRATCH_ZPWORD1
|
||||||
|
clc
|
||||||
|
adc #5
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
bcc +
|
||||||
|
inc c64.SCRATCH_ZPWORD1+1
|
||||||
|
+ ldy c64.SCRATCH_ZPREG
|
||||||
|
dey
|
||||||
|
cpy #255
|
||||||
|
bne -
|
||||||
|
jmp push_fac1_as_result
|
||||||
|
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_min_f .proc
|
||||||
|
lda #1
|
||||||
|
sta func_max_f._minmax_cmp+1
|
||||||
|
lda #<_largest_pos_float
|
||||||
|
ldy #>_largest_pos_float
|
||||||
|
jmp func_max_f._minmax_entry
|
||||||
|
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_sum_f .proc
|
||||||
|
lda #<FL_ZERO
|
||||||
|
ldy #>FL_ZERO
|
||||||
|
jsr MOVFM
|
||||||
|
jsr prog8_lib.pop_array_and_lengthmin1Y
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
- sty c64.SCRATCH_ZPREG
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
ldy c64.SCRATCH_ZPWORD1+1
|
||||||
|
jsr FADD
|
||||||
|
ldy c64.SCRATCH_ZPREG
|
||||||
|
dey
|
||||||
|
cpy #255
|
||||||
|
beq +
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
clc
|
||||||
|
adc #5
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
bcc -
|
||||||
|
inc c64.SCRATCH_ZPWORD1+1
|
||||||
|
bne -
|
||||||
|
+ jmp push_fac1_as_result
|
||||||
|
.pend
|
||||||
|
|
||||||
|
sign_f .proc
|
||||||
|
jsr pop_float_fac1
|
||||||
|
jsr SIGN
|
||||||
|
sta c64.ESTACK_LO,x
|
||||||
|
dex
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
set_0_array_float .proc
|
||||||
|
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc c64.ESTACK_LO,x
|
||||||
|
tay
|
||||||
|
lda #0
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
iny
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
iny
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
iny
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
iny
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
set_array_float .proc
|
||||||
|
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc c64.ESTACK_LO,x
|
||||||
|
clc
|
||||||
|
adc c64.SCRATCH_ZPWORD2
|
||||||
|
ldy c64.SCRATCH_ZPWORD2+1
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
+ jmp copy_float
|
||||||
|
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||||
|
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
swap_floats .proc
|
||||||
|
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||||
|
ldy #4
|
||||||
|
- lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pha
|
||||||
|
lda (c64.SCRATCH_ZPWORD2),y
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pla
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
dey
|
||||||
|
bpl -
|
||||||
|
rts
|
||||||
|
.pend
|
@ -34,6 +34,7 @@ c64flt {
|
|||||||
&float FL_PIHALF = $e2e0 ; PI / 2
|
&float FL_PIHALF = $e2e0 ; PI / 2
|
||||||
&float FL_TWOPI = $e2e5 ; 2 * PI
|
&float FL_TWOPI = $e2e5 ; 2 * PI
|
||||||
&float FL_FR4 = $e2ea ; .25
|
&float FL_FR4 = $e2ea ; .25
|
||||||
|
; oddly enough, 0.0 isn't available in the kernel.
|
||||||
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
|
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
|
||||||
|
|
||||||
|
|
||||||
@ -41,25 +42,25 @@ c64flt {
|
|||||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||||
|
|
||||||
; checked functions below:
|
; checked functions below:
|
||||||
asmsub MOVFM (uword mflpt @ AY) clobbers(A,Y) = $bba2 ; load mflpt value from memory in A/Y into fac1
|
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||||
asmsub FREADMEM () clobbers(A,Y) = $bba6 ; load mflpt value from memory in $22/$23 into fac1
|
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
|
||||||
asmsub CONUPK (uword mflpt @ AY) clobbers(A,Y) = $ba8c ; load mflpt value from memory in A/Y into fac2
|
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||||
asmsub FAREADMEM () clobbers(A,Y) = $ba90 ; load mflpt value from memory in $22/$23 into fac2
|
romsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
|
||||||
asmsub MOVFA () clobbers(A,X) = $bbfc ; copy fac2 to fac1
|
romsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||||
asmsub MOVAF () clobbers(A,X) = $bc0c ; copy fac1 to fac2 (rounded)
|
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||||
asmsub MOVEF () clobbers(A,X) = $bc0f ; copy fac1 to fac2
|
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||||
asmsub MOVMF (uword mflpt @ XY) clobbers(A,Y) = $bbd4 ; store fac1 to memory X/Y as 5-byte mflpt
|
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||||
|
|
||||||
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
||||||
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa ; note: calls AYINT.
|
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
|
||||||
|
|
||||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||||
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||||
asmsub GETADR () clobbers(X) -> ubyte @ Y, ubyte @ A = $b7f7
|
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||||
|
|
||||||
asmsub QINT () clobbers(A,X,Y) = $bc9b ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
||||||
asmsub AYINT () clobbers(A,X,Y) = $b1bf ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||||
|
|
||||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||||
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||||
@ -67,50 +68,49 @@ asmsub AYINT () clobbers(A,X,Y) = $b1bf ; fac1-> signed word in 100-101 ($64
|
|||||||
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||||
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||||
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||||
asmsub GIVAYF (ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y) = $b391
|
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||||
|
|
||||||
asmsub FREADUY (ubyte value @ Y) clobbers(A,X,Y) = $b3a2 ; 8 bit unsigned Y -> float in fac1
|
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
|
||||||
asmsub FREADSA (byte value @ A) clobbers(A,X,Y) = $bc3c ; 8 bit signed A -> float in fac1
|
romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||||
asmsub FREADSTR (ubyte length @ A) clobbers(A,X,Y) = $b7b5 ; str -> fac1, $22/23 must point to string, A=string length
|
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length
|
||||||
asmsub FPRINTLN () clobbers(A,X,Y) = $aabc ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
|
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
|
||||||
asmsub FOUT () clobbers(X) -> uword @ AY = $bddd ; fac1 -> string, address returned in AY ($0100)
|
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
|
||||||
|
|
||||||
asmsub FADDH () clobbers(A,X,Y) = $b849 ; fac1 += 0.5, for rounding- call this before INT
|
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||||
asmsub MUL10 () clobbers(A,X,Y) = $bae2 ; fac1 *= 10
|
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||||
asmsub DIV10 () clobbers(A,X,Y) = $bafe ; fac1 /= 10 , CAUTION: result is always positive!
|
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||||
asmsub FCOMP (uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A = $bc5b ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
romsub $bc5b = 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
|
||||||
|
|
||||||
asmsub FADDT () clobbers(A,X,Y) = $b86a ; fac1 += fac2
|
romsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||||
asmsub FADD (uword mflpt @ AY) clobbers(A,X,Y) = $b867 ; fac1 += mflpt value from A/Y
|
romsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||||
asmsub FSUBT () clobbers(A,X,Y) = $b853 ; fac1 = fac2-fac1 mind the order of the operands
|
romsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||||
asmsub FSUB (uword mflpt @ AY) clobbers(A,X,Y) = $b850 ; fac1 = mflpt from A/Y - fac1
|
romsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||||
asmsub FMULTT () clobbers(A,X,Y) = $ba2b ; fac1 *= fac2
|
romsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||||
asmsub FMULT (uword mflpt @ AY) clobbers(A,X,Y) = $ba28 ; fac1 *= mflpt value from A/Y
|
romsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||||
asmsub FDIVT () clobbers(A,X,Y) = $bb12 ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
||||||
asmsub FDIV (uword mflpt @ AY) clobbers(A,X,Y) = $bb0f ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||||
asmsub FPWRT () clobbers(A,X,Y) = $bf7b ; fac1 = fac2 ** fac1
|
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||||
asmsub FPWR (uword mflpt @ AY) clobbers(A,X,Y) = $bf78 ; fac1 = fac2 ** mflpt from A/Y
|
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||||
|
|
||||||
asmsub NOTOP () clobbers(A,X,Y) = $aed4 ; fac1 = NOT(fac1)
|
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
|
||||||
asmsub INT () clobbers(A,X,Y) = $bccc ; INT() truncates, use FADDH first to round instead of trunc
|
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||||
asmsub LOG () clobbers(A,X,Y) = $b9ea ; fac1 = LN(fac1) (natural log)
|
romsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||||
asmsub SGN () clobbers(A,X,Y) = $bc39 ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
romsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||||
asmsub SIGN () -> ubyte @ A = $bc2b ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
romsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||||
asmsub ABS () = $bc58 ; fac1 = ABS(fac1)
|
romsub $bc58 = ABS() ; fac1 = ABS(fac1)
|
||||||
asmsub SQR () clobbers(A,X,Y) = $bf71 ; fac1 = SQRT(fac1)
|
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||||
asmsub SQRA () clobbers(A,X,Y) = $bf74 ; fac1 = SQRT(fac2)
|
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
|
||||||
asmsub EXP () clobbers(A,X,Y) = $bfed ; fac1 = EXP(fac1) (e ** fac1)
|
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||||
asmsub NEGOP () clobbers(A) = $bfb4 ; switch the sign of fac1
|
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1
|
||||||
asmsub RND () clobbers(A,X,Y) = $e097 ; fac1 = RND(fac1) float random number generator
|
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||||
asmsub COS () clobbers(A,X,Y) = $e264 ; fac1 = COS(fac1)
|
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||||
asmsub SIN () clobbers(A,X,Y) = $e26b ; fac1 = SIN(fac1)
|
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||||
asmsub TAN () clobbers(A,X,Y) = $e2b4 ; fac1 = TAN(fac1)
|
romsub $e2b4 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||||
asmsub ATN () clobbers(A,X,Y) = $e30e ; fac1 = ATN(fac1)
|
romsub $e30e = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
asmsub FREADS32() clobbers(A,X,Y) {
|
||||||
asmsub FREADS32 () clobbers(A,X,Y) {
|
|
||||||
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
|
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
|
||||||
%asm {{
|
%asm {{
|
||||||
lda $62
|
lda $62
|
||||||
@ -210,8 +210,8 @@ sub print_fln (float value) {
|
|||||||
; ---- prints the floating point value (with a newline at the end) using basic rom routines
|
; ---- prints the floating point value (with a newline at the end) using basic rom routines
|
||||||
%asm {{
|
%asm {{
|
||||||
stx c64.SCRATCH_ZPREGX
|
stx c64.SCRATCH_ZPREGX
|
||||||
lda #<print_fln_value
|
lda #<value
|
||||||
ldy #>print_fln_value
|
ldy #>value
|
||||||
jsr MOVFM ; load float into fac1
|
jsr MOVFM ; load float into fac1
|
||||||
jsr FPRINTLN ; print fac1 with newline
|
jsr FPRINTLN ; print fac1 with newline
|
||||||
ldx c64.SCRATCH_ZPREGX
|
ldx c64.SCRATCH_ZPREGX
|
||||||
@ -220,750 +220,6 @@ sub print_fln (float value) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%asminclude "library:c64floats.asm", ""
|
||||||
; --- low level floating point assembly routines
|
|
||||||
%asm {{
|
|
||||||
ub2float .proc
|
|
||||||
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
|
||||||
; clobbers A, Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
ldy c64.SCRATCH_ZPB1
|
|
||||||
jsr FREADUY
|
|
||||||
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
|
|
||||||
ldy c64.SCRATCH_ZPWORD2+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
b2float .proc
|
|
||||||
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
|
||||||
; clobbers A, Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
lda c64.SCRATCH_ZPB1
|
|
||||||
jsr FREADSA
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
uw2float .proc
|
|
||||||
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr GIVUAYFAY
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
w2float .proc
|
|
||||||
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1
|
|
||||||
lda c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr GIVAYF
|
|
||||||
jmp ub2float._fac_to_mem
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_b2float .proc
|
|
||||||
; -- b2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr FREADSA
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_w2float .proc
|
|
||||||
; -- w2float operating on the stack
|
|
||||||
inx
|
|
||||||
ldy c64.ESTACK_LO,x
|
|
||||||
lda c64.ESTACK_HI,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr GIVAYF
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_ub2float .proc
|
|
||||||
; -- ub2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
tay
|
|
||||||
jsr FREADUY
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_uw2float .proc
|
|
||||||
; -- uw2float operating on the stack
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
ldy c64.ESTACK_HI,x
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr GIVUAYFAY
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_float2w .proc ; also used for float2b
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr AYINT
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
lda $64
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
lda $65
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
stack_float2uw .proc ; also used for float2ub
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr GETADR
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
tya
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
push_float .proc
|
|
||||||
; ---- push mflpt5 in A/Y onto stack
|
|
||||||
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
ldy #0
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_HI,x
|
|
||||||
dex
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_rndf .proc
|
|
||||||
; -- put a random floating point value on the stack
|
|
||||||
stx c64.SCRATCH_ZPREG
|
|
||||||
lda #1
|
|
||||||
jsr FREADSA
|
|
||||||
jsr RND ; rng into fac1
|
|
||||||
ldx #<_rndf_rnum5
|
|
||||||
ldy #>_rndf_rnum5
|
|
||||||
jsr MOVMF ; fac1 to mem X/Y
|
|
||||||
ldx c64.SCRATCH_ZPREG
|
|
||||||
lda #<_rndf_rnum5
|
|
||||||
ldy #>_rndf_rnum5
|
|
||||||
jmp push_float
|
|
||||||
_rndf_rnum5 .byte 0,0,0,0,0
|
|
||||||
.pend
|
|
||||||
|
|
||||||
push_float_from_indexed_var .proc
|
|
||||||
; -- push the float from the array at A/Y with index on stack, onto the stack.
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr prog8_lib.pop_index_times_5
|
|
||||||
jsr prog8_lib.add_a_to_zpword
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jmp push_float
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float .proc
|
|
||||||
; ---- pops mflpt5 from stack to memory A/Y
|
|
||||||
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
ldy #4
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_HI,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_HI,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
lda c64.ESTACK_LO,x
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float_fac1 .proc
|
|
||||||
; -- pops float from stack into FAC1
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jmp MOVFM
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pop_float_to_indexed_var .proc
|
|
||||||
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr prog8_lib.pop_index_times_5
|
|
||||||
jsr prog8_lib.add_a_to_zpword
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jmp pop_float
|
|
||||||
.pend
|
|
||||||
|
|
||||||
copy_float .proc
|
|
||||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
|
||||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
|
||||||
sta c64.SCRATCH_ZPWORD2
|
|
||||||
sty c64.SCRATCH_ZPWORD2+1
|
|
||||||
ldy #0
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
iny
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
sta (c64.SCRATCH_ZPWORD2),y
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
inc_var_f .proc
|
|
||||||
; -- add 1 to float pointed to by A/Y
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr MOVFM
|
|
||||||
lda #<FL_FONE
|
|
||||||
ldy #>FL_FONE
|
|
||||||
jsr FADD
|
|
||||||
ldx c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
dec_var_f .proc
|
|
||||||
; -- subtract 1 from float pointed to by A/Y
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
sty c64.SCRATCH_ZPWORD1+1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<FL_FONE
|
|
||||||
ldy #>FL_FONE
|
|
||||||
jsr MOVFM
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr FSUB
|
|
||||||
ldx c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr MOVMF
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
inc_indexed_var_f .proc
|
|
||||||
; -- add 1 to float in array pointed to by A/Y, at index X
|
|
||||||
pha
|
|
||||||
txa
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
pla
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
bcc +
|
|
||||||
iny
|
|
||||||
+ jmp inc_var_f
|
|
||||||
.pend
|
|
||||||
|
|
||||||
dec_indexed_var_f .proc
|
|
||||||
; -- subtract 1 to float in array pointed to by A/Y, at index X
|
|
||||||
pha
|
|
||||||
txa
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
pla
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1
|
|
||||||
bcc +
|
|
||||||
iny
|
|
||||||
+ jmp dec_var_f
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
pop_2_floats_f2_in_fac1 .proc
|
|
||||||
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jmp MOVFM
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
|
||||||
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
|
|
||||||
|
|
||||||
push_fac1_as_result .proc
|
|
||||||
; -- push the float in FAC1 onto the stack, and return from calculation
|
|
||||||
ldx #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVMF
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
jmp push_float
|
|
||||||
.pend
|
|
||||||
|
|
||||||
pow_f .proc
|
|
||||||
; -- push f1 ** f2 on stack
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr CONUPK ; fac2 = float1
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr FPWR
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
div_f .proc
|
|
||||||
; -- push f1/f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FDIV
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
add_f .proc
|
|
||||||
; -- push f1+f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FADD
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
sub_f .proc
|
|
||||||
; -- push f1-f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FSUB
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
mul_f .proc
|
|
||||||
; -- push f1*f2 on stack
|
|
||||||
jsr pop_2_floats_f2_in_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FMULT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
neg_f .proc
|
|
||||||
; -- push -flt back on stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr NEGOP
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
abs_f .proc
|
|
||||||
; -- push abs(float) on stack (as float)
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr ABS
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
equal_f .proc
|
|
||||||
; -- are the two mflpt5 numbers on the stack identical?
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO-3,x
|
|
||||||
cmp c64.ESTACK_LO,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_LO-2,x
|
|
||||||
cmp c64.ESTACK_LO+1,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_LO-1,x
|
|
||||||
cmp c64.ESTACK_LO+2,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_HI-2,x
|
|
||||||
cmp c64.ESTACK_HI+1,x
|
|
||||||
bne _equals_false
|
|
||||||
lda c64.ESTACK_HI-1,x
|
|
||||||
cmp c64.ESTACK_HI+2,x
|
|
||||||
bne _equals_false
|
|
||||||
_equals_true lda #1
|
|
||||||
_equals_store inx
|
|
||||||
sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
_equals_false lda #0
|
|
||||||
beq _equals_store
|
|
||||||
.pend
|
|
||||||
|
|
||||||
notequal_f .proc
|
|
||||||
; -- are the two mflpt5 numbers on the stack different?
|
|
||||||
jsr equal_f
|
|
||||||
eor #1 ; invert the result
|
|
||||||
sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
less_f .proc
|
|
||||||
; -- is f1 < f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #255
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
lesseq_f .proc
|
|
||||||
; -- is f1 <= f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #255
|
|
||||||
beq compare_floats._return_true
|
|
||||||
cmp #0
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
greater_f .proc
|
|
||||||
; -- is f1 > f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #1
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
greatereq_f .proc
|
|
||||||
; -- is f1 >= f2?
|
|
||||||
jsr compare_floats
|
|
||||||
cmp #1
|
|
||||||
beq compare_floats._return_true
|
|
||||||
cmp #0
|
|
||||||
beq compare_floats._return_true
|
|
||||||
bne compare_floats._return_false
|
|
||||||
.pend
|
|
||||||
|
|
||||||
compare_floats .proc
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr pop_float
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVFM ; fac1 = flt1
|
|
||||||
lda #<fmath_float2
|
|
||||||
ldy #>fmath_float2
|
|
||||||
stx c64.SCRATCH_ZPREG
|
|
||||||
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
|
||||||
ldx c64.SCRATCH_ZPREG
|
|
||||||
rts
|
|
||||||
_return_false lda #0
|
|
||||||
_return_result sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
_return_true lda #1
|
|
||||||
bne _return_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_sin .proc
|
|
||||||
; -- push sin(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr SIN
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_cos .proc
|
|
||||||
; -- push cos(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr COS
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_tan .proc
|
|
||||||
; -- push tan(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr TAN
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_atan .proc
|
|
||||||
; -- push atan(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr ATN
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_ln .proc
|
|
||||||
; -- push ln(f) back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr LOG
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_log2 .proc
|
|
||||||
; -- push log base 2, ln(f)/ln(2), back onto stack
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr LOG
|
|
||||||
jsr MOVEF
|
|
||||||
lda #<c64.FL_LOG2
|
|
||||||
ldy #>c64.FL_LOG2
|
|
||||||
jsr MOVFM
|
|
||||||
jsr FDIVT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_sqrt .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr SQR
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_rad .proc
|
|
||||||
; -- convert degrees to radians (d * pi / 180)
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<_pi_div_180
|
|
||||||
ldy #>_pi_div_180
|
|
||||||
jsr FMULT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_deg .proc
|
|
||||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
lda #<_one_over_pi_div_180
|
|
||||||
ldy #>_one_over_pi_div_180
|
|
||||||
jsr FMULT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_round .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr FADDH
|
|
||||||
jsr INT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_floor .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
jsr INT
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_ceil .proc
|
|
||||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
|
||||||
jsr pop_float_fac1
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
ldx #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr MOVMF
|
|
||||||
jsr INT
|
|
||||||
lda #<fmath_float1
|
|
||||||
ldy #>fmath_float1
|
|
||||||
jsr FCOMP
|
|
||||||
cmp #0
|
|
||||||
beq +
|
|
||||||
lda #<FL_FONE
|
|
||||||
ldy #>FL_FONE
|
|
||||||
jsr FADD
|
|
||||||
+ jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_any_f .proc
|
|
||||||
inx
|
|
||||||
lda c64.ESTACK_LO,x ; array size
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
|
||||||
jmp prog8_lib.func_any_b._entry
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_all_f .proc
|
|
||||||
inx
|
|
||||||
jsr prog8_lib.peek_address
|
|
||||||
lda c64.ESTACK_LO,x ; array size
|
|
||||||
sta c64.SCRATCH_ZPB1
|
|
||||||
asl a
|
|
||||||
asl a
|
|
||||||
clc
|
|
||||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
|
||||||
tay
|
|
||||||
dey
|
|
||||||
- lda (c64.SCRATCH_ZPWORD1),y
|
|
||||||
clc
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
adc (c64.SCRATCH_ZPWORD1),y
|
|
||||||
dey
|
|
||||||
cmp #0
|
|
||||||
beq +
|
|
||||||
cpy #255
|
|
||||||
bne -
|
|
||||||
lda #1
|
|
||||||
sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
+ sta c64.ESTACK_LO+1,x
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_max_f .proc
|
|
||||||
lda #255
|
|
||||||
sta _minmax_cmp+1
|
|
||||||
lda #<_largest_neg_float
|
|
||||||
ldy #>_largest_neg_float
|
|
||||||
_minmax_entry jsr MOVFM
|
|
||||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
- sty c64.SCRATCH_ZPREG
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr FCOMP
|
|
||||||
_minmax_cmp cmp #255 ; modified
|
|
||||||
bne +
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr MOVFM
|
|
||||||
+ lda c64.SCRATCH_ZPWORD1
|
|
||||||
clc
|
|
||||||
adc #5
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
bcc +
|
|
||||||
inc c64.SCRATCH_ZPWORD1+1
|
|
||||||
+ ldy c64.SCRATCH_ZPREG
|
|
||||||
dey
|
|
||||||
cpy #255
|
|
||||||
bne -
|
|
||||||
jmp push_fac1_as_result
|
|
||||||
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_min_f .proc
|
|
||||||
lda #1
|
|
||||||
sta func_max_f._minmax_cmp+1
|
|
||||||
lda #<_largest_pos_float
|
|
||||||
ldy #>_largest_pos_float
|
|
||||||
jmp func_max_f._minmax_entry
|
|
||||||
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
func_sum_f .proc
|
|
||||||
lda #<FL_ZERO
|
|
||||||
ldy #>FL_ZERO
|
|
||||||
jsr MOVFM
|
|
||||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
|
||||||
stx c64.SCRATCH_ZPREGX
|
|
||||||
- sty c64.SCRATCH_ZPREG
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
ldy c64.SCRATCH_ZPWORD1+1
|
|
||||||
jsr FADD
|
|
||||||
ldy c64.SCRATCH_ZPREG
|
|
||||||
dey
|
|
||||||
cpy #255
|
|
||||||
beq +
|
|
||||||
lda c64.SCRATCH_ZPWORD1
|
|
||||||
clc
|
|
||||||
adc #5
|
|
||||||
sta c64.SCRATCH_ZPWORD1
|
|
||||||
bcc -
|
|
||||||
inc c64.SCRATCH_ZPWORD1+1
|
|
||||||
bne -
|
|
||||||
+ jmp push_fac1_as_result
|
|
||||||
.pend
|
|
||||||
|
|
||||||
sign_f .proc
|
|
||||||
jsr pop_float_fac1
|
|
||||||
jsr SIGN
|
|
||||||
sta c64.ESTACK_LO,x
|
|
||||||
dex
|
|
||||||
rts
|
|
||||||
.pend
|
|
||||||
|
|
||||||
|
|
||||||
}}
|
|
||||||
|
|
||||||
} ; ------ end of block c64flt
|
} ; ------ end of block c64flt
|
||||||
|
@ -186,8 +186,8 @@ c64 {
|
|||||||
|
|
||||||
; ---- C64 basic routines ----
|
; ---- C64 basic routines ----
|
||||||
|
|
||||||
asmsub CLEARSCR () clobbers(A,X,Y) = $E544 ; clear the screen
|
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
||||||
asmsub HOMECRSR () clobbers(A,X,Y) = $E566 ; cursor to top left of screen
|
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||||
|
|
||||||
|
|
||||||
; ---- end of C64 basic routines ----
|
; ---- end of C64 basic routines ----
|
||||||
@ -195,48 +195,48 @@ asmsub HOMECRSR () clobbers(A,X,Y) = $E566 ; cursor to top left of screen
|
|||||||
|
|
||||||
; ---- C64 kernal routines ----
|
; ---- C64 kernal routines ----
|
||||||
|
|
||||||
asmsub STROUT (uword strptr @ AY) clobbers(A, X, Y) = $AB1E ; print null-terminated string (use c64scr.print instead)
|
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
|
||||||
asmsub IRQDFRT () clobbers(A,X,Y) = $EA31 ; default IRQ routine
|
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||||
asmsub IRQDFEND () clobbers(A,X,Y) = $EA81 ; default IRQ end/cleanup
|
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||||
asmsub CINT () clobbers(A,X,Y) = $FF81 ; (alias: SCINIT) initialize screen editor and video chip
|
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||||
asmsub IOINIT () clobbers(A, X) = $FF84 ; initialize I/O devices (CIA, SID, IRQ)
|
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||||
asmsub RAMTAS () clobbers(A,X,Y) = $FF87 ; initialize RAM, tape buffer, screen
|
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||||
asmsub RESTOR () clobbers(A,X,Y) = $FF8A ; restore default I/O vectors
|
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||||
asmsub VECTOR (uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) = $FF8D ; read/set I/O vector table
|
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||||
asmsub SETMSG (ubyte value @ A) = $FF90 ; set Kernal message control flag
|
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||||
asmsub SECOND (ubyte address @ A) clobbers(A) = $FF93 ; (alias: LSTNSA) send secondary address after LISTEN
|
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||||
asmsub TKSA (ubyte address @ A) clobbers(A) = $FF96 ; (alias: TALKSA) send secondary address after TALK
|
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||||
asmsub MEMTOP (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF99 ; read/set top of memory pointer
|
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||||
asmsub MEMBOT (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF9C ; read/set bottom of memory pointer
|
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||||
asmsub SCNKEY () clobbers(A,X,Y) = $FF9F ; scan the keyboard
|
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||||
asmsub SETTMO (ubyte timeout @ A) = $FFA2 ; set time-out flag for IEEE bus
|
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||||
asmsub ACPTR () -> ubyte @ A = $FFA5 ; (alias: IECIN) input byte from serial bus
|
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||||
asmsub CIOUT (ubyte databyte @ A) = $FFA8 ; (alias: IECOUT) output byte to serial bus
|
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||||
asmsub UNTLK () clobbers(A) = $FFAB ; command serial bus device to UNTALK
|
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||||
asmsub UNLSN () clobbers(A) = $FFAE ; command serial bus device to UNLISTEN
|
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||||
asmsub LISTEN (ubyte device @ A) clobbers(A) = $FFB1 ; command serial bus device to LISTEN
|
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||||
asmsub TALK (ubyte device @ A) clobbers(A) = $FFB4 ; command serial bus device to TALK
|
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||||
asmsub READST () -> ubyte @ A = $FFB7 ; read I/O status word
|
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||||
asmsub SETLFS (ubyte logical @ A, ubyte device @ X, ubyte address @ Y) = $FFBA ; set logical file parameters
|
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||||
asmsub SETNAM (ubyte namelen @ A, str filename @ XY) = $FFBD ; set filename parameters
|
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||||
asmsub OPEN () clobbers(A,X,Y) = $FFC0 ; (via 794 ($31A)) open a logical file
|
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
|
||||||
asmsub CLOSE (ubyte logical @ A) clobbers(A,X,Y) = $FFC3 ; (via 796 ($31C)) close a logical file
|
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||||
asmsub CHKIN (ubyte logical @ X) clobbers(A,X) = $FFC6 ; (via 798 ($31E)) define an input channel
|
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
|
||||||
asmsub CHKOUT (ubyte logical @ X) clobbers(A,X) = $FFC9 ; (via 800 ($320)) define an output channel
|
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||||
asmsub CLRCHN () clobbers(A,X) = $FFCC ; (via 802 ($322)) restore default devices
|
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||||
asmsub CHRIN () clobbers(Y) -> ubyte @ A = $FFCF ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||||
asmsub CHROUT (ubyte char @ A) = $FFD2 ; (via 806 ($326)) output a character
|
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||||
asmsub LOAD (ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y = $FFD5 ; (via 816 ($330)) load from device
|
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||||
asmsub SAVE (ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A = $FFD8 ; (via 818 ($332)) save to a device
|
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||||
asmsub SETTIM (ubyte low @ A, ubyte middle @ X, ubyte high @ Y) = $FFDB ; set the software clock
|
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||||
asmsub RDTIM () -> ubyte @ A, ubyte @ X, ubyte @ Y = $FFDE ; read the software clock
|
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||||
asmsub STOP () clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc = $FFE1 ; (via 808 ($328)) check the STOP key
|
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
|
||||||
asmsub GETIN () clobbers(X,Y) -> ubyte @ A = $FFE4 ; (via 810 ($32A)) get a character
|
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
|
||||||
asmsub CLALL () clobbers(A,X) = $FFE7 ; (via 812 ($32C)) close all files
|
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||||
asmsub UDTIM () clobbers(A,X) = $FFEA ; update the software clock
|
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||||
asmsub SCREEN () -> ubyte @ X, ubyte @ Y = $FFED ; read number of screen rows and columns
|
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||||
asmsub PLOT (ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
|
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
|
||||||
asmsub IOBASE () -> uword @ XY = $FFF3 ; read base address of I/O devices
|
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||||
|
|
||||||
; ---- end of C64 kernal routines ----
|
; ---- end of C64 kernal routines ----
|
||||||
|
|
||||||
|
@ -565,14 +565,9 @@ asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
|||||||
%asm {{
|
%asm {{
|
||||||
ldy #0
|
ldy #0
|
||||||
_loop sta c64.Screen,y
|
_loop sta c64.Screen,y
|
||||||
sta c64.Screen+1,y
|
|
||||||
sta c64.Screen+$0100,y
|
sta c64.Screen+$0100,y
|
||||||
sta c64.Screen+$0101,y
|
|
||||||
sta c64.Screen+$0200,y
|
sta c64.Screen+$0200,y
|
||||||
sta c64.Screen+$0201,y
|
|
||||||
sta c64.Screen+$02e8,y
|
sta c64.Screen+$02e8,y
|
||||||
sta c64.Screen+$02e9,y
|
|
||||||
iny
|
|
||||||
iny
|
iny
|
||||||
bne _loop
|
bne _loop
|
||||||
rts
|
rts
|
||||||
@ -585,14 +580,9 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
|||||||
%asm {{
|
%asm {{
|
||||||
ldy #0
|
ldy #0
|
||||||
_loop sta c64.Colors,y
|
_loop sta c64.Colors,y
|
||||||
sta c64.Colors+1,y
|
|
||||||
sta c64.Colors+$0100,y
|
sta c64.Colors+$0100,y
|
||||||
sta c64.Colors+$0101,y
|
|
||||||
sta c64.Colors+$0200,y
|
sta c64.Colors+$0200,y
|
||||||
sta c64.Colors+$0201,y
|
|
||||||
sta c64.Colors+$02e8,y
|
sta c64.Colors+$02e8,y
|
||||||
sta c64.Colors+$02e9,y
|
|
||||||
iny
|
|
||||||
iny
|
iny
|
||||||
bne _loop
|
bne _loop
|
||||||
rts
|
rts
|
||||||
@ -603,6 +593,7 @@ asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
|||||||
; ---- scroll the whole screen 1 character to the left
|
; ---- scroll the whole screen 1 character to the left
|
||||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||||
; Carry flag determines if screen color data must be scrolled too
|
; Carry flag determines if screen color data must be scrolled too
|
||||||
|
|
||||||
%asm {{
|
%asm {{
|
||||||
stx c64.SCRATCH_ZPREGX
|
stx c64.SCRATCH_ZPREGX
|
||||||
bcs +
|
bcs +
|
||||||
@ -612,18 +603,7 @@ asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
|||||||
ldx #0
|
ldx #0
|
||||||
ldy #38
|
ldy #38
|
||||||
-
|
-
|
||||||
.for row=0, row<=12, row+=1
|
.for row=0, row<=24, row+=1
|
||||||
lda c64.Colors + 40*row + 1,x
|
|
||||||
sta c64.Colors + 40*row,x
|
|
||||||
.next
|
|
||||||
inx
|
|
||||||
dey
|
|
||||||
bpl -
|
|
||||||
|
|
||||||
ldx #0
|
|
||||||
ldy #38
|
|
||||||
-
|
|
||||||
.for row=13, row<=24, row+=1
|
|
||||||
lda c64.Colors + 40*row + 1,x
|
lda c64.Colors + 40*row + 1,x
|
||||||
sta c64.Colors + 40*row,x
|
sta c64.Colors + 40*row,x
|
||||||
.next
|
.next
|
||||||
@ -635,18 +615,7 @@ _scroll_screen ; scroll the screen memory
|
|||||||
ldx #0
|
ldx #0
|
||||||
ldy #38
|
ldy #38
|
||||||
-
|
-
|
||||||
.for row=0, row<=12, row+=1
|
.for row=0, row<=24, row+=1
|
||||||
lda c64.Screen + 40*row + 1,x
|
|
||||||
sta c64.Screen + 40*row,x
|
|
||||||
.next
|
|
||||||
inx
|
|
||||||
dey
|
|
||||||
bpl -
|
|
||||||
|
|
||||||
ldx #0
|
|
||||||
ldy #38
|
|
||||||
-
|
|
||||||
.for row=13, row<=24, row+=1
|
|
||||||
lda c64.Screen + 40*row + 1,x
|
lda c64.Screen + 40*row + 1,x
|
||||||
sta c64.Screen + 40*row,x
|
sta c64.Screen + 40*row,x
|
||||||
.next
|
.next
|
||||||
@ -671,41 +640,23 @@ asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
|
|||||||
+ ; scroll the color memory
|
+ ; scroll the color memory
|
||||||
ldx #38
|
ldx #38
|
||||||
-
|
-
|
||||||
.for row=0, row<=12, row+=1
|
.for row=0, row<=24, row+=1
|
||||||
lda c64.Colors + 40*row + 0,x
|
lda c64.Colors + 40*row + 0,x
|
||||||
sta c64.Colors + 40*row + 1,x
|
sta c64.Colors + 40*row + 1,x
|
||||||
.next
|
.next
|
||||||
dex
|
dex
|
||||||
bpl -
|
bpl -
|
||||||
|
|
||||||
ldx #38
|
|
||||||
-
|
|
||||||
.for row=13, row<=24, row+=1
|
|
||||||
lda c64.Colors + 40*row,x
|
|
||||||
sta c64.Colors + 40*row + 1,x
|
|
||||||
.next
|
|
||||||
dex
|
|
||||||
bpl -
|
|
||||||
|
|
||||||
_scroll_screen ; scroll the screen memory
|
_scroll_screen ; scroll the screen memory
|
||||||
ldx #38
|
ldx #38
|
||||||
-
|
-
|
||||||
.for row=0, row<=12, row+=1
|
.for row=0, row<=24, row+=1
|
||||||
lda c64.Screen + 40*row + 0,x
|
lda c64.Screen + 40*row + 0,x
|
||||||
sta c64.Screen + 40*row + 1,x
|
sta c64.Screen + 40*row + 1,x
|
||||||
.next
|
.next
|
||||||
dex
|
dex
|
||||||
bpl -
|
bpl -
|
||||||
|
|
||||||
ldx #38
|
|
||||||
-
|
|
||||||
.for row=13, row<=24, row+=1
|
|
||||||
lda c64.Screen + 40*row,x
|
|
||||||
sta c64.Screen + 40*row + 1,x
|
|
||||||
.next
|
|
||||||
dex
|
|
||||||
bpl -
|
|
||||||
|
|
||||||
ldx c64.SCRATCH_ZPREGX
|
ldx c64.SCRATCH_ZPREGX
|
||||||
rts
|
rts
|
||||||
}}
|
}}
|
||||||
@ -723,16 +674,7 @@ asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
|
|||||||
+ ; scroll the color memory
|
+ ; scroll the color memory
|
||||||
ldx #39
|
ldx #39
|
||||||
-
|
-
|
||||||
.for row=1, row<=11, row+=1
|
.for row=1, row<=24, row+=1
|
||||||
lda c64.Colors + 40*row,x
|
|
||||||
sta c64.Colors + 40*(row-1),x
|
|
||||||
.next
|
|
||||||
dex
|
|
||||||
bpl -
|
|
||||||
|
|
||||||
ldx #39
|
|
||||||
-
|
|
||||||
.for row=12, row<=24, row+=1
|
|
||||||
lda c64.Colors + 40*row,x
|
lda c64.Colors + 40*row,x
|
||||||
sta c64.Colors + 40*(row-1),x
|
sta c64.Colors + 40*(row-1),x
|
||||||
.next
|
.next
|
||||||
@ -742,16 +684,7 @@ asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
|
|||||||
_scroll_screen ; scroll the screen memory
|
_scroll_screen ; scroll the screen memory
|
||||||
ldx #39
|
ldx #39
|
||||||
-
|
-
|
||||||
.for row=1, row<=11, row+=1
|
.for row=1, row<=24, row+=1
|
||||||
lda c64.Screen + 40*row,x
|
|
||||||
sta c64.Screen + 40*(row-1),x
|
|
||||||
.next
|
|
||||||
dex
|
|
||||||
bpl -
|
|
||||||
|
|
||||||
ldx #39
|
|
||||||
-
|
|
||||||
.for row=12, row<=24, row+=1
|
|
||||||
lda c64.Screen + 40*row,x
|
lda c64.Screen + 40*row,x
|
||||||
sta c64.Screen + 40*(row-1),x
|
sta c64.Screen + 40*(row-1),x
|
||||||
.next
|
.next
|
||||||
@ -775,16 +708,7 @@ asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
|
|||||||
+ ; scroll the color memory
|
+ ; scroll the color memory
|
||||||
ldx #39
|
ldx #39
|
||||||
-
|
-
|
||||||
.for row=23, row>=12, row-=1
|
.for row=23, row>=0, row-=1
|
||||||
lda c64.Colors + 40*row,x
|
|
||||||
sta c64.Colors + 40*(row+1),x
|
|
||||||
.next
|
|
||||||
dex
|
|
||||||
bpl -
|
|
||||||
|
|
||||||
ldx #39
|
|
||||||
-
|
|
||||||
.for row=11, row>=0, row-=1
|
|
||||||
lda c64.Colors + 40*row,x
|
lda c64.Colors + 40*row,x
|
||||||
sta c64.Colors + 40*(row+1),x
|
sta c64.Colors + 40*(row+1),x
|
||||||
.next
|
.next
|
||||||
@ -794,16 +718,7 @@ asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
|
|||||||
_scroll_screen ; scroll the screen memory
|
_scroll_screen ; scroll the screen memory
|
||||||
ldx #39
|
ldx #39
|
||||||
-
|
-
|
||||||
.for row=23, row>=12, row-=1
|
.for row=23, row>=0, row-=1
|
||||||
lda c64.Screen + 40*row,x
|
|
||||||
sta c64.Screen + 40*(row+1),x
|
|
||||||
.next
|
|
||||||
dex
|
|
||||||
bpl -
|
|
||||||
|
|
||||||
ldx #39
|
|
||||||
-
|
|
||||||
.for row=11, row>=0, row-=1
|
|
||||||
lda c64.Screen + 40*row,x
|
lda c64.Screen + 40*row,x
|
||||||
sta c64.Screen + 40*(row+1),x
|
sta c64.Screen + 40*(row+1),x
|
||||||
.next
|
.next
|
||||||
@ -862,11 +777,14 @@ _print_byte_digits
|
|||||||
beq +
|
beq +
|
||||||
tya
|
tya
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
|
pla
|
||||||
|
jsr c64.CHROUT
|
||||||
|
jmp _ones
|
||||||
+ pla
|
+ pla
|
||||||
cmp #'0'
|
cmp #'0'
|
||||||
beq +
|
beq _ones
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
+ txa
|
_ones txa
|
||||||
jsr c64.CHROUT
|
jsr c64.CHROUT
|
||||||
ldx c64.SCRATCH_ZPREGX
|
ldx c64.SCRATCH_ZPREGX
|
||||||
rts
|
rts
|
||||||
@ -975,6 +893,7 @@ asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
|||||||
%asm {{
|
%asm {{
|
||||||
stx c64.SCRATCH_ZPREGX
|
stx c64.SCRATCH_ZPREGX
|
||||||
jsr c64utils.uword2decimal
|
jsr c64utils.uword2decimal
|
||||||
|
ldx c64.SCRATCH_ZPREGX
|
||||||
ldy #0
|
ldy #0
|
||||||
- lda c64utils.uword2decimal.decTenThousands,y
|
- lda c64utils.uword2decimal.decTenThousands,y
|
||||||
beq _allzero
|
beq _allzero
|
||||||
|
@ -680,3 +680,192 @@ _sign_possibly_zero lda c64.ESTACK_LO+1,x
|
|||||||
sta c64.ESTACK_LO+1,x
|
sta c64.ESTACK_LO+1,x
|
||||||
rts
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
; bit shifts.
|
||||||
|
; anything below 3 is done inline. anything above 7 is done via other optimizations.
|
||||||
|
|
||||||
|
shift_left_w_7 .proc
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
|
||||||
|
asl a
|
||||||
|
rol c64.SCRATCH_ZPB1
|
||||||
|
_shift6 asl a
|
||||||
|
rol c64.SCRATCH_ZPB1
|
||||||
|
_shift5 asl a
|
||||||
|
rol c64.SCRATCH_ZPB1
|
||||||
|
_shift4 asl a
|
||||||
|
rol c64.SCRATCH_ZPB1
|
||||||
|
_shift3 asl a
|
||||||
|
rol c64.SCRATCH_ZPB1
|
||||||
|
asl a
|
||||||
|
rol c64.SCRATCH_ZPB1
|
||||||
|
asl a
|
||||||
|
rol c64.SCRATCH_ZPB1
|
||||||
|
|
||||||
|
sta c64.ESTACK_LO+1,x
|
||||||
|
lda c64.SCRATCH_ZPB1
|
||||||
|
sta c64.ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_left_w_6 .proc
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
jmp shift_left_w_7._shift6
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_left_w_5 .proc
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
jmp shift_left_w_7._shift5
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_left_w_4 .proc
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
jmp shift_left_w_7._shift4
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_left_w_3 .proc
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
jmp shift_left_w_7._shift3
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_right_uw_7 .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
|
||||||
|
lsr a
|
||||||
|
ror c64.SCRATCH_ZPB1
|
||||||
|
_shift6 lsr a
|
||||||
|
ror c64.SCRATCH_ZPB1
|
||||||
|
_shift5 lsr a
|
||||||
|
ror c64.SCRATCH_ZPB1
|
||||||
|
_shift4 lsr a
|
||||||
|
ror c64.SCRATCH_ZPB1
|
||||||
|
_shift3 lsr a
|
||||||
|
ror c64.SCRATCH_ZPB1
|
||||||
|
lsr a
|
||||||
|
ror c64.SCRATCH_ZPB1
|
||||||
|
lsr a
|
||||||
|
ror c64.SCRATCH_ZPB1
|
||||||
|
|
||||||
|
sta c64.ESTACK_HI+1,x
|
||||||
|
lda c64.SCRATCH_ZPB1
|
||||||
|
sta c64.ESTACK_LO+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_right_uw_6 .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
jmp shift_right_uw_7._shift6
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_right_uw_5 .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
jmp shift_right_uw_7._shift5
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_right_uw_4 .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
jmp shift_right_uw_7._shift4
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_right_uw_3 .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
jmp shift_right_uw_7._shift3
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
shift_right_w_7 .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
|
||||||
|
asl a
|
||||||
|
ror c64.SCRATCH_ZPWORD1+1
|
||||||
|
ror c64.SCRATCH_ZPWORD1
|
||||||
|
|
||||||
|
lda c64.SCRATCH_ZPWORD1+1
|
||||||
|
_shift6 asl a
|
||||||
|
ror c64.SCRATCH_ZPWORD1+1
|
||||||
|
ror c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.SCRATCH_ZPWORD1+1
|
||||||
|
_shift5 asl a
|
||||||
|
ror c64.SCRATCH_ZPWORD1+1
|
||||||
|
ror c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.SCRATCH_ZPWORD1+1
|
||||||
|
_shift4 asl a
|
||||||
|
ror c64.SCRATCH_ZPWORD1+1
|
||||||
|
ror c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.SCRATCH_ZPWORD1+1
|
||||||
|
_shift3 asl a
|
||||||
|
ror c64.SCRATCH_ZPWORD1+1
|
||||||
|
ror c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.SCRATCH_ZPWORD1+1
|
||||||
|
asl a
|
||||||
|
ror c64.SCRATCH_ZPWORD1+1
|
||||||
|
ror c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.SCRATCH_ZPWORD1+1
|
||||||
|
asl a
|
||||||
|
ror c64.SCRATCH_ZPWORD1+1
|
||||||
|
ror c64.SCRATCH_ZPWORD1
|
||||||
|
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
sta c64.ESTACK_LO+1,x
|
||||||
|
lda c64.SCRATCH_ZPWORD1+1
|
||||||
|
sta c64.ESTACK_HI+1,x
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_right_w_6 .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
jmp shift_right_w_7._shift6
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_right_w_5 .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
jmp shift_right_w_7._shift5
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_right_w_4 .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
jmp shift_right_w_7._shift4
|
||||||
|
.pend
|
||||||
|
|
||||||
|
shift_right_w_3 .proc
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI+1,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
jmp shift_right_w_7._shift3
|
||||||
|
.pend
|
||||||
|
|
||||||
|
@ -651,6 +651,18 @@ greatereq_w .proc
|
|||||||
bmi equal_b._equal_b_false
|
bmi equal_b._equal_b_false
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
|
||||||
|
|
||||||
|
func_exit .proc
|
||||||
|
; -- immediately exit the program with a return code in the A register
|
||||||
|
lda c64.ESTACK_LO+1,x
|
||||||
|
ldx orig_stackpointer
|
||||||
|
txs
|
||||||
|
rts ; return to original caller
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
func_read_flags .proc
|
func_read_flags .proc
|
||||||
; -- put the processor status register on the stack
|
; -- put the processor status register on the stack
|
||||||
php
|
php
|
||||||
@ -1577,96 +1589,182 @@ _work3 .word 0
|
|||||||
reverse_b .proc
|
reverse_b .proc
|
||||||
; --- reverse an array of bytes (in-place)
|
; --- reverse an array of bytes (in-place)
|
||||||
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
|
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
|
||||||
_left_index = c64.SCRATCH_ZPWORD2
|
_index_right = c64.SCRATCH_ZPWORD2
|
||||||
_right_index = c64.SCRATCH_ZPWORD2+1
|
_index_left = c64.SCRATCH_ZPWORD2+1
|
||||||
pha
|
_loop_count = c64.SCRATCH_ZPREG
|
||||||
|
sta _loop_count
|
||||||
|
lsr _loop_count
|
||||||
sec
|
sec
|
||||||
sbc #1
|
sbc #1
|
||||||
sta _left_index
|
sta _index_right
|
||||||
lda #0
|
lda #0
|
||||||
|
sta _index_left
|
||||||
|
_loop ldy _index_right
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pha
|
||||||
|
ldy _index_left
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ldy _index_right
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pla
|
||||||
|
ldy _index_left
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
inc _index_left
|
||||||
|
dec _index_right
|
||||||
|
dec _loop_count
|
||||||
|
bne _loop
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
reverse_f .proc
|
||||||
|
; --- reverse an array of floats
|
||||||
|
_left_index = c64.SCRATCH_ZPWORD2
|
||||||
|
_right_index = c64.SCRATCH_ZPWORD2+1
|
||||||
|
_loop_count = c64.SCRATCH_ZPREG
|
||||||
|
pha
|
||||||
|
sta c64.SCRATCH_ZPREG
|
||||||
|
asl a
|
||||||
|
asl a
|
||||||
|
clc
|
||||||
|
adc c64.SCRATCH_ZPREG ; *5 because float
|
||||||
|
sec
|
||||||
|
sbc #5
|
||||||
sta _right_index
|
sta _right_index
|
||||||
|
lda #0
|
||||||
|
sta _left_index
|
||||||
pla
|
pla
|
||||||
lsr a
|
lsr a
|
||||||
tay
|
sta _loop_count
|
||||||
_loop sty c64.SCRATCH_ZPREG
|
_loop ; push the left indexed float on the stack
|
||||||
ldy _left_index
|
ldy _left_index
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
pha
|
pha
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pha
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
pha
|
||||||
|
; copy right index float to left index float
|
||||||
ldy _right_index
|
ldy _right_index
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
ldy _left_index
|
ldy _left_index
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
pla
|
inc _left_index
|
||||||
ldy _right_index
|
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
|
||||||
inc _right_index
|
inc _right_index
|
||||||
dec _left_index
|
ldy _right_index
|
||||||
ldy c64.SCRATCH_ZPREG
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
inc _left_index
|
||||||
|
inc _right_index
|
||||||
|
ldy _right_index
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ldy _left_index
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
; pop the float off the stack into the right index float
|
||||||
|
ldy _right_index
|
||||||
|
pla
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
dey
|
dey
|
||||||
|
pla
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
pla
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
inc _left_index
|
||||||
|
lda _right_index
|
||||||
|
sec
|
||||||
|
sbc #9
|
||||||
|
sta _right_index
|
||||||
|
dec _loop_count
|
||||||
bne _loop
|
bne _loop
|
||||||
rts
|
rts
|
||||||
|
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
|
|
||||||
reverse_w .proc
|
reverse_w .proc
|
||||||
; --- reverse an array of words (in-place)
|
; --- reverse an array of words (in-place)
|
||||||
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
|
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
|
||||||
_left_index = c64.SCRATCH_ZPWORD2
|
_index_first = c64.SCRATCH_ZPWORD2
|
||||||
_right_index = c64.SCRATCH_ZPWORD2+1
|
_index_second = c64.SCRATCH_ZPWORD2+1
|
||||||
|
_loop_count = c64.SCRATCH_ZPREG
|
||||||
pha
|
pha
|
||||||
asl a ; *2 because words
|
asl a ; *2 because words
|
||||||
sec
|
sec
|
||||||
sbc #2
|
sbc #2
|
||||||
sta _left_index
|
sta _index_first
|
||||||
lda #0
|
lda #0
|
||||||
sta _right_index
|
sta _index_second
|
||||||
pla
|
pla
|
||||||
lsr a
|
lsr a
|
||||||
pha
|
pha
|
||||||
tay
|
sta _loop_count
|
||||||
; first reverse the lsbs
|
; first reverse the lsbs
|
||||||
_loop_lo sty c64.SCRATCH_ZPREG
|
_loop_lo ldy _index_first
|
||||||
ldy _left_index
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
pha
|
pha
|
||||||
ldy _right_index
|
ldy _index_second
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
ldy _left_index
|
ldy _index_first
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
pla
|
pla
|
||||||
ldy _right_index
|
ldy _index_second
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
inc _right_index
|
inc _index_second
|
||||||
inc _right_index
|
inc _index_second
|
||||||
dec _left_index
|
dec _index_first
|
||||||
dec _left_index
|
dec _index_first
|
||||||
ldy c64.SCRATCH_ZPREG
|
dec _loop_count
|
||||||
dey
|
|
||||||
bne _loop_lo
|
bne _loop_lo
|
||||||
; now reverse the msbs
|
; now reverse the msbs
|
||||||
dec _right_index
|
dec _index_second
|
||||||
inc _left_index
|
inc _index_first
|
||||||
inc _left_index
|
inc _index_first
|
||||||
inc _left_index
|
inc _index_first
|
||||||
pla
|
pla
|
||||||
tay
|
sta _loop_count
|
||||||
_loop_hi sty c64.SCRATCH_ZPREG
|
_loop_hi ldy _index_first
|
||||||
ldy _left_index
|
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
pha
|
pha
|
||||||
ldy _right_index
|
ldy _index_second
|
||||||
lda (c64.SCRATCH_ZPWORD1),y
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
ldy _left_index
|
ldy _index_first
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
pla
|
pla
|
||||||
ldy _right_index
|
ldy _index_second
|
||||||
sta (c64.SCRATCH_ZPWORD1),y
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
dec _right_index
|
dec _index_second
|
||||||
dec _right_index
|
dec _index_second
|
||||||
inc _left_index
|
inc _index_first
|
||||||
inc _left_index
|
inc _index_first
|
||||||
ldy c64.SCRATCH_ZPREG
|
dec _loop_count
|
||||||
dey
|
|
||||||
bne _loop_hi
|
bne _loop_hi
|
||||||
|
|
||||||
rts
|
rts
|
||||||
@ -1690,7 +1788,6 @@ ror2_mem_ub .proc
|
|||||||
|
|
||||||
rol2_mem_ub .proc
|
rol2_mem_ub .proc
|
||||||
; -- in-place 8-bit rol of byte at memory location on stack
|
; -- in-place 8-bit rol of byte at memory location on stack
|
||||||
;" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}"
|
|
||||||
inx
|
inx
|
||||||
lda c64.ESTACK_LO,x
|
lda c64.ESTACK_LO,x
|
||||||
sta c64.SCRATCH_ZPWORD1
|
sta c64.SCRATCH_ZPWORD1
|
||||||
@ -1705,57 +1802,403 @@ rol2_mem_ub .proc
|
|||||||
.pend
|
.pend
|
||||||
|
|
||||||
lsl_array_b .proc
|
lsl_array_b .proc
|
||||||
.warn "lsl_array_b" ; TODO
|
; -- lsl a (u)byte in an array (index and array address on stack)
|
||||||
.pend
|
inx
|
||||||
|
ldy c64.ESTACK_LO,x
|
||||||
lsl_array_w .proc
|
inx
|
||||||
.warn "lsl_array_w" ; TODO
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
asl a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
lsr_array_ub .proc
|
lsr_array_ub .proc
|
||||||
.warn "lsr_array_ub" ; TODO
|
; -- lsr a ubyte in an array (index and array address on stack)
|
||||||
|
inx
|
||||||
|
ldy c64.ESTACK_LO,x
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
lsr a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
lsr_array_b .proc
|
lsr_array_b .proc
|
||||||
.warn "lsr_array_b" ; TODO
|
; -- lsr a byte in an array (index and array address on stack)
|
||||||
|
inx
|
||||||
|
ldy c64.ESTACK_LO,x
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
asl a
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ror a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
lsl_array_w .proc
|
||||||
|
; -- lsl a (u)word in an array (index and array address on stack)
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
asl a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rol a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
lsr_array_uw .proc
|
lsr_array_uw .proc
|
||||||
.warn "lsr_array_uw" ; TODO
|
; -- lsr a uword in an array (index and array address on stack)
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
lsr a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ror a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
lsr_array_w .proc
|
lsr_array_w .proc
|
||||||
.warn "lsr_array_w" ; TODO
|
; -- lsr a uword in an array (index and array address on stack)
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
asl a
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ror a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ror a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
rol_array_ub .proc
|
rol_array_ub .proc
|
||||||
.warn "rol_array_ub" ; TODO
|
; -- rol a ubyte in an array (index and array address on stack)
|
||||||
|
inx
|
||||||
|
ldy c64.ESTACK_LO,x
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rol a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
rol_array_uw .proc
|
|
||||||
.warn "rol_array_uw" ; TODO
|
|
||||||
.pend
|
|
||||||
|
|
||||||
rol2_array_ub .proc
|
|
||||||
.warn "rol2_array_ub" ; TODO
|
|
||||||
.pend
|
|
||||||
|
|
||||||
rol2_array_uw .proc
|
|
||||||
.warn "rol2_array_uw" ; TODO
|
|
||||||
.pend
|
|
||||||
|
|
||||||
ror_array_ub .proc
|
ror_array_ub .proc
|
||||||
.warn "ror_array_ub" ; TODO
|
; -- ror a ubyte in an array (index and array address on stack)
|
||||||
.pend
|
inx
|
||||||
|
ldy c64.ESTACK_LO,x
|
||||||
ror_array_uw .proc
|
inx
|
||||||
.warn "ror_array_uw" ; TODO
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ror a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
ror2_array_ub .proc
|
ror2_array_ub .proc
|
||||||
.warn "ror2_array_ub" ; TODO
|
; -- ror2 (8-bit ror) a ubyte in an array (index and array address on stack)
|
||||||
|
inx
|
||||||
|
ldy c64.ESTACK_LO,x
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
lsr a
|
||||||
|
bcc +
|
||||||
|
ora #$80
|
||||||
|
+ sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol2_array_ub .proc
|
||||||
|
; -- rol2 (8-bit rol) a ubyte in an array (index and array address on stack)
|
||||||
|
inx
|
||||||
|
ldy c64.ESTACK_LO,x
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
cmp #$80
|
||||||
|
rol a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
ror_array_uw .proc
|
||||||
|
; -- ror a uword in an array (index and array address on stack)
|
||||||
|
php
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
plp
|
||||||
|
ror a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ror a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol_array_uw .proc
|
||||||
|
; -- rol a uword in an array (index and array address on stack)
|
||||||
|
php
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
plp
|
||||||
|
rol a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rol a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
rol2_array_uw .proc
|
||||||
|
; -- rol2 (16-bit rol) a uword in an array (index and array address on stack)
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
asl a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
rol a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
bcc +
|
||||||
|
dey
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
adc #0
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
+ rts
|
||||||
.pend
|
.pend
|
||||||
|
|
||||||
ror2_array_uw .proc
|
ror2_array_uw .proc
|
||||||
.warn "ror2_array_uw" ; TODO
|
; -- ror2 (16-bit ror) a uword in an array (index and array address on stack)
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
asl a
|
||||||
|
tay
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
lsr a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
dey
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ror a
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
bcc +
|
||||||
|
iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
ora #$80
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
+ rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
strcpy .proc
|
||||||
|
; copy a string (0-terminated) from A/Y to (ZPWORD1)
|
||||||
|
; it is assumed the target string is large enough.
|
||||||
|
sta c64.SCRATCH_ZPWORD2
|
||||||
|
sty c64.SCRATCH_ZPWORD2+1
|
||||||
|
ldy #$ff
|
||||||
|
- iny
|
||||||
|
lda (c64.SCRATCH_ZPWORD2),y
|
||||||
|
sta (c64.SCRATCH_ZPWORD1),y
|
||||||
|
bne -
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
|
||||||
|
func_leftstr .proc
|
||||||
|
; leftstr(source, target, length) with params on stack
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
tay ; length
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD2
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD2+1
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
lda #0
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
- dey
|
||||||
|
cpy #$ff
|
||||||
|
bne +
|
||||||
|
rts
|
||||||
|
+ lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
jmp -
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_rightstr .proc
|
||||||
|
; rightstr(source, target, length) with params on stack
|
||||||
|
; make place for the 4 parameters for substr()
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
dex
|
||||||
|
; X-> .
|
||||||
|
; x+1 -> length of segment
|
||||||
|
; x+2 -> start index
|
||||||
|
; X+3 -> target LO+HI
|
||||||
|
; X+4 -> source LO+HI
|
||||||
|
; original parameters:
|
||||||
|
; x+5 -> original length LO
|
||||||
|
; x+6 -> original targetLO + HI
|
||||||
|
; x+7 -> original sourceLO + HI
|
||||||
|
; replicate paramters:
|
||||||
|
lda c64.ESTACK_LO+5,x
|
||||||
|
sta c64.ESTACK_LO+1,x
|
||||||
|
lda c64.ESTACK_LO+6,x
|
||||||
|
sta c64.ESTACK_LO+3,x
|
||||||
|
lda c64.ESTACK_HI+6,x
|
||||||
|
sta c64.ESTACK_HI+3,x
|
||||||
|
lda c64.ESTACK_LO+7,x
|
||||||
|
sta c64.ESTACK_LO+4,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI+7,x
|
||||||
|
sta c64.ESTACK_HI+4,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
; determine string length
|
||||||
|
ldy #0
|
||||||
|
- lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
beq +
|
||||||
|
iny
|
||||||
|
bne -
|
||||||
|
+ tya
|
||||||
|
sec
|
||||||
|
sbc c64.ESTACK_LO+1,x ; start index = strlen - segment length
|
||||||
|
sta c64.ESTACK_LO+2,x
|
||||||
|
jsr func_substr
|
||||||
|
; unwind original params
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
inx
|
||||||
|
rts
|
||||||
|
.pend
|
||||||
|
|
||||||
|
func_substr .proc
|
||||||
|
; substr(source, target, start, length) with params on stack
|
||||||
|
inx
|
||||||
|
ldy c64.ESTACK_LO,x ; length
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x ; start
|
||||||
|
sta c64.SCRATCH_ZPB1
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD2
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD2+1
|
||||||
|
inx
|
||||||
|
lda c64.ESTACK_LO,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
lda c64.ESTACK_HI,x
|
||||||
|
sta c64.SCRATCH_ZPWORD1+1
|
||||||
|
; adjust src location
|
||||||
|
clc
|
||||||
|
lda c64.SCRATCH_ZPWORD1
|
||||||
|
adc c64.SCRATCH_ZPB1
|
||||||
|
sta c64.SCRATCH_ZPWORD1
|
||||||
|
bcc +
|
||||||
|
inc c64.SCRATCH_ZPWORD1+1
|
||||||
|
+ lda #0
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
jmp _startloop
|
||||||
|
- lda (c64.SCRATCH_ZPWORD1),y
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
_startloop dey
|
||||||
|
cpy #$ff
|
||||||
|
bne -
|
||||||
|
rts
|
||||||
|
|
||||||
.pend
|
.pend
|
||||||
|
@ -1 +1 @@
|
|||||||
1.70
|
3.0
|
||||||
|
@ -2,13 +2,13 @@ package prog8
|
|||||||
|
|
||||||
import kotlinx.cli.*
|
import kotlinx.cli.*
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.AstException
|
||||||
import prog8.compiler.*
|
import prog8.compiler.CompilationResult
|
||||||
|
import prog8.compiler.compileProgram
|
||||||
import prog8.compiler.target.CompilationTarget
|
import prog8.compiler.target.CompilationTarget
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.c64.Petscii
|
||||||
import prog8.compiler.target.c64.codegen.AsmGen
|
import prog8.compiler.target.c64.codegen.AsmGen
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
import prog8.vm.astvm.AstVm
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.FileSystems
|
import java.nio.file.FileSystems
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -39,7 +39,6 @@ private fun compileMain(args: Array<String>) {
|
|||||||
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
|
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
|
||||||
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
||||||
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
|
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
|
||||||
val launchSimulator by cli.flagArgument("-sim", "launch the builtin execution simulator after compilation")
|
|
||||||
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||||
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently only 'c64' (C64 6502 assembly) available", "c64")
|
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently only 'c64' (C64 6502 assembly) available", "c64")
|
||||||
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
|
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
|
||||||
@ -55,8 +54,12 @@ private fun compileMain(args: Array<String>) {
|
|||||||
with(CompilationTarget) {
|
with(CompilationTarget) {
|
||||||
name = "c64"
|
name = "c64"
|
||||||
machine = C64MachineDefinition
|
machine = C64MachineDefinition
|
||||||
encodeString = { str -> Petscii.encodePetscii(str, true) }
|
encodeString = { str, altEncoding ->
|
||||||
decodeString = { bytes -> Petscii.decodePetscii(bytes, true) }
|
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
|
}
|
||||||
|
decodeString = { bytes, altEncoding ->
|
||||||
|
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||||
|
}
|
||||||
asmGenerator = ::AsmGen
|
asmGenerator = ::AsmGen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,18 +117,6 @@ private fun compileMain(args: Array<String>) {
|
|||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (launchSimulator) {
|
|
||||||
// val c64 = razorvine.c64emu.C64Machine("C64 emulator launched from Prog8 compiler")
|
|
||||||
// c64.cpu.addBreakpoint(0xea31) { cpu, address ->
|
|
||||||
// println("zz")
|
|
||||||
// Cpu6502.BreakpointResultAction()
|
|
||||||
// }
|
|
||||||
// c64.start()
|
|
||||||
println("\nLaunching AST-based simulator...")
|
|
||||||
val vm = AstVm(compilationResult.programAst, compilationTarget)
|
|
||||||
vm.run()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (startEmulator) {
|
if (startEmulator) {
|
||||||
if (compilationResult.programName.isEmpty())
|
if (compilationResult.programName.isEmpty())
|
||||||
println("\nCan't start emulator because no program was assembled.")
|
println("\nCan't start emulator because no program was assembled.")
|
||||||
|
@ -102,6 +102,12 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl) {
|
override fun visit(decl: VarDecl) {
|
||||||
|
|
||||||
|
// if the vardecl is a parameter of a subroutine, don't output it again
|
||||||
|
val paramNames = (decl.definingScope() as? Subroutine)?.parameters?.map { it.name }
|
||||||
|
if(paramNames!=null && decl.name in paramNames)
|
||||||
|
return
|
||||||
|
|
||||||
when(decl.type) {
|
when(decl.type) {
|
||||||
VarDeclType.VAR -> {}
|
VarDeclType.VAR -> {}
|
||||||
VarDeclType.CONST -> output("const ")
|
VarDeclType.CONST -> output("const ")
|
||||||
@ -177,8 +183,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
|
|
||||||
private fun outputStatements(statements: List<Statement>) {
|
private fun outputStatements(statements: List<Statement>) {
|
||||||
for(stmt in statements) {
|
for(stmt in statements) {
|
||||||
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
|
|
||||||
continue // skip autogenerated decls (to avoid generating a newline)
|
|
||||||
outputi("")
|
outputi("")
|
||||||
stmt.accept(this)
|
stmt.accept(this)
|
||||||
output("\n")
|
output("\n")
|
||||||
@ -283,16 +287,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignment: Assignment) {
|
override fun visit(assignment: Assignment) {
|
||||||
if(assignment is VariableInitializationAssignment) {
|
|
||||||
val targetVar = assignment.target.identifier?.targetVarDecl(program.namespace)
|
|
||||||
if(targetVar?.struct != null) {
|
|
||||||
// skip STRUCT init assignments
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assignment.target.accept(this)
|
assignment.target.accept(this)
|
||||||
if (assignment.aug_op != null)
|
if (assignment.aug_op != null && assignment.aug_op != "setvalue")
|
||||||
output(" ${assignment.aug_op} ")
|
output(" ${assignment.aug_op} ")
|
||||||
else
|
else
|
||||||
output(" = ")
|
output(" = ")
|
||||||
@ -314,10 +310,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
|
|
||||||
override fun visit(forLoop: ForLoop) {
|
override fun visit(forLoop: ForLoop) {
|
||||||
output("for ")
|
output("for ")
|
||||||
if(forLoop.loopRegister!=null)
|
forLoop.loopVar.accept(this)
|
||||||
output(forLoop.loopRegister.toString())
|
|
||||||
else
|
|
||||||
forLoop.loopVar!!.accept(this)
|
|
||||||
output(" in ")
|
output(" in ")
|
||||||
forLoop.iterable.accept(this)
|
forLoop.iterable.accept(this)
|
||||||
output(" ")
|
output(" ")
|
||||||
@ -333,9 +326,16 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
|
|
||||||
override fun visit(repeatLoop: RepeatLoop) {
|
override fun visit(repeatLoop: RepeatLoop) {
|
||||||
output("repeat ")
|
output("repeat ")
|
||||||
|
repeatLoop.iterations?.accept(this)
|
||||||
|
output(" ")
|
||||||
repeatLoop.body.accept(this)
|
repeatLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(untilLoop: UntilLoop) {
|
||||||
|
output("do ")
|
||||||
|
untilLoop.body.accept(this)
|
||||||
output(" until ")
|
output(" until ")
|
||||||
repeatLoop.untilCondition.accept(this)
|
untilLoop.untilCondition.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(returnStmt: Return) {
|
override fun visit(returnStmt: Return) {
|
||||||
@ -351,12 +351,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignTarget: AssignTarget) {
|
override fun visit(assignTarget: AssignTarget) {
|
||||||
if(assignTarget.register!=null)
|
|
||||||
output(assignTarget.register.toString())
|
|
||||||
else {
|
|
||||||
assignTarget.memoryAddress?.accept(this)
|
assignTarget.memoryAddress?.accept(this)
|
||||||
assignTarget.identifier?.accept(this)
|
assignTarget.identifier?.accept(this)
|
||||||
}
|
|
||||||
assignTarget.arrayindexed?.accept(this)
|
assignTarget.arrayindexed?.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,10 +393,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
outputlni("}}")
|
outputlni("}}")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(registerExpr: RegisterExpr) {
|
|
||||||
output(registerExpr.register.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||||
output(builtinFunctionStatementPlaceholder.name)
|
output(builtinFunctionStatementPlaceholder.name)
|
||||||
}
|
}
|
||||||
@ -435,10 +427,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
|||||||
outputln("")
|
outputln("")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(structLv: StructLiteralValue) {
|
|
||||||
outputListMembers(structLv.values.asSequence(), '{', '}')
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(nopStatement: NopStatement) {
|
override fun visit(nopStatement: NopStatement) {
|
||||||
output("; NOP @ ${nopStatement.position} $nopStatement")
|
output("; NOP @ ${nopStatement.position} $nopStatement")
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package prog8.ast
|
|||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.Expression
|
import prog8.ast.expressions.Expression
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstVisitor
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -33,6 +35,8 @@ interface Node {
|
|||||||
return this
|
return this
|
||||||
throw FatalAstException("scope missing from $this")
|
throw FatalAstException("scope missing from $this")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun replaceChildNode(node: Node, replacement: Node)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IFunctionCall {
|
interface IFunctionCall {
|
||||||
@ -48,32 +52,31 @@ interface INameScope {
|
|||||||
|
|
||||||
fun linkParents(parent: Node)
|
fun linkParents(parent: Node)
|
||||||
|
|
||||||
fun subScopes(): Map<String, INameScope> {
|
fun subScope(name: String): INameScope? {
|
||||||
val subscopes = mutableMapOf<String, INameScope>()
|
|
||||||
for(stmt in statements) {
|
for(stmt in statements) {
|
||||||
when(stmt) {
|
when(stmt) {
|
||||||
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
||||||
is ForLoop -> subscopes[stmt.body.name] = stmt.body
|
is ForLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
|
is UntilLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
|
is WhileLoop -> if(stmt.body.name==name) return stmt.body
|
||||||
is BranchStatement -> {
|
is BranchStatement -> {
|
||||||
subscopes[stmt.truepart.name] = stmt.truepart
|
if(stmt.truepart.name==name) return stmt.truepart
|
||||||
if(stmt.elsepart.containsCodeOrVars())
|
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
|
||||||
}
|
}
|
||||||
is IfStatement -> {
|
is IfStatement -> {
|
||||||
subscopes[stmt.truepart.name] = stmt.truepart
|
if(stmt.truepart.name==name) return stmt.truepart
|
||||||
if(stmt.elsepart.containsCodeOrVars())
|
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
|
||||||
}
|
}
|
||||||
is WhenStatement -> {
|
is WhenStatement -> {
|
||||||
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
|
val scope = stmt.choices.firstOrNull { it.statements.name==name }
|
||||||
|
if(scope!=null)
|
||||||
|
return scope.statements
|
||||||
}
|
}
|
||||||
is INameScope -> subscopes[stmt.name] = stmt
|
is INameScope -> if(stmt.name==name) return stmt
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return subscopes
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLabelOrVariable(name: String): Statement? {
|
fun getLabelOrVariable(name: String): Statement? {
|
||||||
@ -121,7 +124,7 @@ interface INameScope {
|
|||||||
for(module in localContext.definingModule().program.modules) {
|
for(module in localContext.definingModule().program.modules) {
|
||||||
var scope: INameScope? = module
|
var scope: INameScope? = module
|
||||||
for(name in scopedName.dropLast(1)) {
|
for(name in scopedName.dropLast(1)) {
|
||||||
scope = scope?.subScopes()?.get(name)
|
scope = scope?.subScope(name)
|
||||||
if(scope==null)
|
if(scope==null)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -129,7 +132,7 @@ interface INameScope {
|
|||||||
val result = scope.getLabelOrVariable(scopedName.last())
|
val result = scope.getLabelOrVariable(scopedName.last())
|
||||||
if(result!=null)
|
if(result!=null)
|
||||||
return result
|
return result
|
||||||
return scope.subScopes()[scopedName.last()] as Statement?
|
return scope.subScope(scopedName.last()) as Statement?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -141,7 +144,7 @@ interface INameScope {
|
|||||||
val result = localScope.getLabelOrVariable(scopedName[0])
|
val result = localScope.getLabelOrVariable(scopedName[0])
|
||||||
if (result != null)
|
if (result != null)
|
||||||
return result
|
return result
|
||||||
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
|
val subscope = localScope.subScope(scopedName[0]) as Statement?
|
||||||
if (subscope != null)
|
if (subscope != null)
|
||||||
return subscope
|
return subscope
|
||||||
// not found in this scope, look one higher up
|
// not found in this scope, look one higher up
|
||||||
@ -152,12 +155,46 @@ interface INameScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
||||||
|
fun containsNoVars() = statements.all { it !is VarDecl }
|
||||||
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
||||||
|
|
||||||
fun remove(stmt: Statement) {
|
fun remove(stmt: Statement) {
|
||||||
if(!statements.remove(stmt))
|
if(!statements.remove(stmt))
|
||||||
throw FatalAstException("stmt to remove wasn't found in scope")
|
throw FatalAstException("stmt to remove wasn't found in scope")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllLabels(label: String): List<Label> {
|
||||||
|
val result = mutableListOf<Label>()
|
||||||
|
|
||||||
|
fun find(scope: INameScope) {
|
||||||
|
scope.statements.forEach {
|
||||||
|
when(it) {
|
||||||
|
is Label -> result.add(it)
|
||||||
|
is INameScope -> find(it)
|
||||||
|
is IfStatement -> {
|
||||||
|
find(it.truepart)
|
||||||
|
find(it.elsepart)
|
||||||
|
}
|
||||||
|
is UntilLoop -> find(it.body)
|
||||||
|
is RepeatLoop -> find(it.body)
|
||||||
|
is WhileLoop -> find(it.body)
|
||||||
|
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
|
||||||
|
else -> { /* do nothing */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
find(this)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextSibling(stmt: Statement): Statement? {
|
||||||
|
val nextIdx = statements.indexOfFirst { it===stmt } + 1
|
||||||
|
return if(nextIdx < statements.size)
|
||||||
|
statements[nextIdx]
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAssignable {
|
interface IAssignable {
|
||||||
@ -167,7 +204,7 @@ interface IAssignable {
|
|||||||
|
|
||||||
/*********** Everything starts from here, the Program; zero or more modules *************/
|
/*********** Everything starts from here, the Program; zero or more modules *************/
|
||||||
|
|
||||||
class Program(val name: String, val modules: MutableList<Module>) {
|
class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||||
val namespace = GlobalNamespace(modules)
|
val namespace = GlobalNamespace(modules)
|
||||||
|
|
||||||
val definedLoadAddress: Int
|
val definedLoadAddress: Int
|
||||||
@ -182,11 +219,29 @@ class Program(val name: String, val modules: MutableList<Module>) {
|
|||||||
return if(mainBlocks.isEmpty()) {
|
return if(mainBlocks.isEmpty()) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
mainBlocks[0].subScopes()["start"] as Subroutine?
|
mainBlocks[0].subScope("start") as Subroutine?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
|
||||||
|
|
||||||
|
override val position: Position = Position.DUMMY
|
||||||
|
override var parent: Node
|
||||||
|
get() = throw FatalAstException("program has no parent")
|
||||||
|
set(value) = throw FatalAstException("can't set parent of program")
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
modules.forEach {
|
||||||
|
it.linkParents(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(node is Module && replacement is Module)
|
||||||
|
val idx = modules.indexOfFirst { it===node }
|
||||||
|
modules[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Module(override val name: String,
|
class Module(override val name: String,
|
||||||
@ -194,6 +249,7 @@ class Module(override val name: String,
|
|||||||
override val position: Position,
|
override val position: Position,
|
||||||
val isLibraryModule: Boolean,
|
val isLibraryModule: Boolean,
|
||||||
val source: Path) : Node, INameScope {
|
val source: Path) : Node, INameScope {
|
||||||
|
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
lateinit var program: Program
|
lateinit var program: Program
|
||||||
val importedBy = mutableListOf<Module>()
|
val importedBy = mutableListOf<Module>()
|
||||||
@ -207,10 +263,20 @@ class Module(override val name: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun definingScope(): INameScope = program.namespace
|
override fun definingScope(): INameScope = program.namespace
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(node is Statement && replacement is Statement)
|
||||||
|
val idx = statements.indexOfFirst { it===node }
|
||||||
|
statements[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
|
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
|
||||||
|
|
||||||
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||||
override val name = "<<<global>>>"
|
override val name = "<<<global>>>"
|
||||||
override val position = Position("<<<global>>>", 0, 0, 0)
|
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||||
@ -221,6 +287,10 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
|||||||
modules.forEach { it.linkParents(this) }
|
modules.forEach { it.linkParents(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("cannot replace anything in the namespace")
|
||||||
|
}
|
||||||
|
|
||||||
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
|
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
|
||||||
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
|
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
|
||||||
// builtin functions always exist, return a dummy localContext for them
|
// builtin functions always exist, return a dummy localContext for them
|
||||||
@ -246,7 +316,7 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
|||||||
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
|
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
|
||||||
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
||||||
null -> null
|
null -> null
|
||||||
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
|
else -> throw SyntaxError("wrong identifier target for $scopedName: $stmt", stmt.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,13 @@ import java.nio.file.Path
|
|||||||
|
|
||||||
private data class NumericLiteral(val number: Number, val datatype: DataType)
|
private data class NumericLiteral(val number: Number, val datatype: DataType)
|
||||||
|
|
||||||
|
internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
|
||||||
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
|
|
||||||
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
|
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
|
||||||
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source)
|
val directives = this.directive().map { it.toAst() }
|
||||||
|
val blocks = this.block().map { it.toAst(isLibrary) }
|
||||||
|
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), isLibrary, source)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun ParserRuleContext.toPosition() : Position {
|
private fun ParserRuleContext.toPosition() : Position {
|
||||||
val customTokensource = this.start.tokenSource as? CustomLexer
|
val customTokensource = this.start.tokenSource as? CustomLexer
|
||||||
val filename =
|
val filename =
|
||||||
@ -38,27 +38,23 @@ private fun ParserRuleContext.toPosition() : Position {
|
|||||||
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
|
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement {
|
||||||
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : Statement {
|
val blockstatements = block_statement().map {
|
||||||
val directive = directive()?.toAst()
|
when {
|
||||||
if(directive!=null) return directive
|
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
|
||||||
|
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
|
||||||
val block = block()?.toAst(isInLibrary)
|
it.directive()!=null -> it.directive().toAst()
|
||||||
if(block!=null) return block
|
it.inlineasm()!=null -> it.inlineasm().toAst()
|
||||||
|
else -> throw FatalAstException("weird block statement $it")
|
||||||
throw FatalAstException(text)
|
}
|
||||||
|
}
|
||||||
|
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement =
|
|
||||||
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
|
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
|
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
|
||||||
statement().asSequence().map { it.toAst() }.toMutableList()
|
statement().asSequence().map { it.toAst() }.toMutableList()
|
||||||
|
|
||||||
|
private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
|
||||||
private fun prog8Parser.StatementContext.toAst() : Statement {
|
|
||||||
vardecl()?.let { return it.toAst() }
|
vardecl()?.let { return it.toAst() }
|
||||||
|
|
||||||
varinitializer()?.let {
|
varinitializer()?.let {
|
||||||
@ -66,7 +62,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
return VarDecl(
|
return VarDecl(
|
||||||
VarDeclType.VAR,
|
VarDeclType.VAR,
|
||||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.varname.text,
|
vd.varname.text,
|
||||||
null,
|
null,
|
||||||
@ -114,7 +110,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
return VarDecl(
|
return VarDecl(
|
||||||
VarDeclType.CONST,
|
VarDeclType.CONST,
|
||||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.varname.text,
|
vd.varname.text,
|
||||||
null,
|
null,
|
||||||
@ -131,7 +127,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
return VarDecl(
|
return VarDecl(
|
||||||
VarDeclType.MEMORY,
|
VarDeclType.MEMORY,
|
||||||
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
vd.datatype()?.toAst() ?: DataType.STRUCT,
|
||||||
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
|
||||||
vd.arrayindex()?.toAst(),
|
vd.arrayindex()?.toAst(),
|
||||||
vd.varname.text,
|
vd.varname.text,
|
||||||
null,
|
null,
|
||||||
@ -142,6 +138,28 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
structdecl()?.let {
|
||||||
|
return StructDecl(it.identifier().text,
|
||||||
|
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
|
||||||
|
toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
throw FatalAstException("weird variable decl $this")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.SubroutinedeclarationContext.toAst() : Subroutine {
|
||||||
|
return when {
|
||||||
|
subroutine()!=null -> subroutine().toAst()
|
||||||
|
asmsubroutine()!=null -> asmsubroutine().toAst()
|
||||||
|
romsubroutine()!=null -> romsubroutine().toAst()
|
||||||
|
else -> throw FatalAstException("weird subroutine decl $this")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||||
|
val vardecl = variabledeclaration()?.toAst()
|
||||||
|
if(vardecl!=null) return vardecl
|
||||||
|
|
||||||
assignment()?.let {
|
assignment()?.let {
|
||||||
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
|
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
|
||||||
}
|
}
|
||||||
@ -175,8 +193,8 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
val returnstmt = returnstmt()?.toAst()
|
val returnstmt = returnstmt()?.toAst()
|
||||||
if(returnstmt!=null) return returnstmt
|
if(returnstmt!=null) return returnstmt
|
||||||
|
|
||||||
val sub = subroutine()?.toAst()
|
val subroutine = subroutinedeclaration()?.toAst()
|
||||||
if(sub!=null) return sub
|
if(subroutine!=null) return subroutine
|
||||||
|
|
||||||
val asm = inlineasm()?.toAst()
|
val asm = inlineasm()?.toAst()
|
||||||
if(asm!=null) return asm
|
if(asm!=null) return asm
|
||||||
@ -187,46 +205,60 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
|||||||
val forloop = forloop()?.toAst()
|
val forloop = forloop()?.toAst()
|
||||||
if(forloop!=null) return forloop
|
if(forloop!=null) return forloop
|
||||||
|
|
||||||
val repeatloop = repeatloop()?.toAst()
|
val untilloop = untilloop()?.toAst()
|
||||||
if(repeatloop!=null) return repeatloop
|
if(untilloop!=null) return untilloop
|
||||||
|
|
||||||
val whileloop = whileloop()?.toAst()
|
val whileloop = whileloop()?.toAst()
|
||||||
if(whileloop!=null) return whileloop
|
if(whileloop!=null) return whileloop
|
||||||
|
|
||||||
|
val repeatloop = repeatloop()?.toAst()
|
||||||
|
if(repeatloop!=null) return repeatloop
|
||||||
|
|
||||||
val breakstmt = breakstmt()?.toAst()
|
val breakstmt = breakstmt()?.toAst()
|
||||||
if(breakstmt!=null) return breakstmt
|
if(breakstmt!=null) return breakstmt
|
||||||
|
|
||||||
val continuestmt = continuestmt()?.toAst()
|
val continuestmt = continuestmt()?.toAst()
|
||||||
if(continuestmt!=null) return continuestmt
|
if(continuestmt!=null) return continuestmt
|
||||||
|
|
||||||
val asmsubstmt = asmsubroutine()?.toAst()
|
|
||||||
if(asmsubstmt!=null) return asmsubstmt
|
|
||||||
|
|
||||||
val whenstmt = whenstmt()?.toAst()
|
val whenstmt = whenstmt()?.toAst()
|
||||||
if(whenstmt!=null) return whenstmt
|
if(whenstmt!=null) return whenstmt
|
||||||
|
|
||||||
structdecl()?.let {
|
|
||||||
return StructDecl(it.identifier().text,
|
|
||||||
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
|
|
||||||
toPosition())
|
|
||||||
}
|
|
||||||
|
|
||||||
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
|
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
|
||||||
|
val subdecl = asmsub_decl().toAst()
|
||||||
|
val statements = statement_block()?.toAst() ?: mutableListOf()
|
||||||
|
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||||
|
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||||
|
subdecl.asmClobbers, null, true, statements, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
|
||||||
|
val subdecl = asmsub_decl().toAst()
|
||||||
|
val address = integerliteral().toAst().number.toInt()
|
||||||
|
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
|
||||||
|
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
|
||||||
|
subdecl.asmClobbers, address, true, mutableListOf(), toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AsmsubDecl(val name: String,
|
||||||
|
val parameters: List<SubroutineParameter>,
|
||||||
|
val returntypes: List<DataType>,
|
||||||
|
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||||
|
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||||
|
val asmClobbers: Set<CpuRegister>)
|
||||||
|
|
||||||
|
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
|
||||||
val name = identifier().text
|
val name = identifier().text
|
||||||
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
|
|
||||||
val params = asmsub_params()?.toAst() ?: emptyList()
|
val params = asmsub_params()?.toAst() ?: emptyList()
|
||||||
val returns = asmsub_returns()?.toAst() ?: emptyList()
|
val returns = asmsub_returns()?.toAst() ?: emptyList()
|
||||||
|
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
|
||||||
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
|
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
|
||||||
val normalReturnvalues = returns.map { it.type }
|
val normalReturntypes = returns.map { it.type }
|
||||||
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
|
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
|
||||||
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
|
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
|
||||||
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
|
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
|
||||||
val statements = statement_block()?.toAst() ?: mutableListOf()
|
|
||||||
return Subroutine(name, normalParameters, normalReturnvalues,
|
|
||||||
paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AsmSubroutineParameter(name: String,
|
private class AsmSubroutineParameter(name: String,
|
||||||
@ -242,27 +274,43 @@ private class AsmSubroutineReturn(val type: DataType,
|
|||||||
val stack: Boolean,
|
val stack: Boolean,
|
||||||
val position: Position)
|
val position: Position)
|
||||||
|
|
||||||
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
|
|
||||||
= this.register().asSequence().map { it.toAst() }.toSet()
|
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||||
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
|
= asmsub_return().map {
|
||||||
|
val register = it.identifier()?.toAst()
|
||||||
|
var registerorpair: RegisterOrPair? = null
|
||||||
|
var statusregister: Statusflag? = null
|
||||||
|
if(register!=null) {
|
||||||
|
when (val name = register.nameInSource.single()) {
|
||||||
|
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
|
||||||
|
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
|
||||||
|
else -> throw FatalAstException("invalid register or status flag in $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AsmSubroutineReturn(
|
||||||
|
it.datatype().toAst(),
|
||||||
|
registerorpair,
|
||||||
|
statusregister,
|
||||||
|
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||||
= asmsub_param().map {
|
= asmsub_param().map {
|
||||||
val vardecl = it.vardecl()
|
val vardecl = it.vardecl()
|
||||||
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
|
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
|
||||||
AsmSubroutineParameter(vardecl.varname.text, datatype,
|
val register = it.identifier()?.toAst()
|
||||||
it.registerorpair()?.toAst(),
|
var registerorpair: RegisterOrPair? = null
|
||||||
it.statusregister()?.toAst(),
|
var statusregister: Statusflag? = null
|
||||||
|
if(register!=null) {
|
||||||
|
when (val name = register.nameInSource.single()) {
|
||||||
|
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
|
||||||
|
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
|
||||||
|
else -> throw FatalAstException("invalid register or status flag in $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister,
|
||||||
!it.stack?.text.isNullOrEmpty(), toPosition())
|
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
|
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
||||||
val void = this.VOID() != null
|
val void = this.VOID() != null
|
||||||
val location = scoped_identifier().toAst()
|
val location = scoped_identifier().toAst()
|
||||||
@ -272,7 +320,6 @@ private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
|||||||
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
|
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
||||||
val location = scoped_identifier().toAst()
|
val location = scoped_identifier().toAst()
|
||||||
return if(expression_list() == null)
|
return if(expression_list() == null)
|
||||||
@ -281,11 +328,9 @@ private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
|
|||||||
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
|
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.InlineasmContext.toAst() =
|
private fun prog8Parser.InlineasmContext.toAst() =
|
||||||
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
InlineAssembly(INLINEASMBLOCK().text, toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
|
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
|
||||||
return Return(expression()?.toAst(), toPosition())
|
return Return(expression()?.toAst(), toPosition())
|
||||||
}
|
}
|
||||||
@ -296,11 +341,9 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
|
|||||||
return Jump(address, identifier, null, toPosition())
|
return Jump(address, identifier, null, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.LabeldefContext.toAst(): Statement =
|
private fun prog8Parser.LabeldefContext.toAst(): Statement =
|
||||||
Label(children[0].text, toPosition())
|
Label(children[0].text, toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
|
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
|
||||||
return Subroutine(identifier().text,
|
return Subroutine(identifier().text,
|
||||||
sub_params()?.toAst() ?: emptyList(),
|
sub_params()?.toAst() ?: emptyList(),
|
||||||
@ -319,44 +362,42 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
|
|||||||
return returns.datatype().map { it.toAst() }
|
return returns.datatype().map { it.toAst() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||||
vardecl().map {
|
vardecl().map {
|
||||||
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
|
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
|
||||||
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
SubroutineParameter(it.varname.text, datatype, it.toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
|
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
|
||||||
val register = register()?.toAst()
|
|
||||||
val identifier = scoped_identifier()
|
val identifier = scoped_identifier()
|
||||||
return when {
|
return when {
|
||||||
register!=null -> AssignTarget(register, null, null, null, toPosition())
|
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
|
||||||
identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition())
|
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
|
||||||
arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition())
|
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
||||||
directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
|
||||||
else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase())
|
private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
|
||||||
|
val names = this.identifier().map { it.toAst().nameInSource.single() }
|
||||||
|
return names.map { CpuRegister.valueOf(it) }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
|
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
|
||||||
|
|
||||||
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
|
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
|
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
|
||||||
ArrayIndex(expression().toAst(), toPosition())
|
ArrayIndex(expression().toAst(), toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.DirectiveContext.toAst() : Directive =
|
private fun prog8Parser.DirectiveContext.toAst() : Directive =
|
||||||
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
|
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
|
||||||
|
|
||||||
|
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
|
||||||
|
val str = stringliteral()
|
||||||
|
if(str?.ALT_STRING_ENCODING() != null)
|
||||||
|
throw AstException("${toPosition()} can't use alternate string encodings for directive arguments")
|
||||||
|
|
||||||
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg =
|
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
|
||||||
DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||||
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
|
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
|
||||||
@ -409,7 +450,6 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||||
|
|
||||||
val litval = literalvalue()
|
val litval = literalvalue()
|
||||||
@ -430,11 +470,13 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
|||||||
else -> throw FatalAstException("invalid datatype for numeric literal")
|
else -> throw FatalAstException("invalid datatype for numeric literal")
|
||||||
}
|
}
|
||||||
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
|
||||||
litval.stringliteral()!=null -> StringLiteralValue(unescape(litval.stringliteral().text, litval.toPosition()), litval.toPosition())
|
litval.stringliteral()!=null -> litval.stringliteral().toAst()
|
||||||
litval.charliteral()!=null -> {
|
litval.charliteral()!=null -> {
|
||||||
try {
|
try {
|
||||||
|
val cc=litval.charliteral()
|
||||||
NumericLiteralValue(DataType.UBYTE, CompilationTarget.encodeString(
|
NumericLiteralValue(DataType.UBYTE, CompilationTarget.encodeString(
|
||||||
unescape(litval.charliteral().text, litval.toPosition()))[0], litval.toPosition())
|
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
|
||||||
|
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
|
||||||
} catch (ce: CharConversionException) {
|
} catch (ce: CharConversionException) {
|
||||||
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
|
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
|
||||||
}
|
}
|
||||||
@ -443,20 +485,13 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
|||||||
val array = litval.arrayliteral().toAst()
|
val array = litval.arrayliteral().toAst()
|
||||||
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
|
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
|
||||||
// the ConstantFold takes care of that and converts the type if needed.
|
// the ConstantFold takes care of that and converts the type if needed.
|
||||||
ArrayLiteralValue(DataType.ARRAY_UB, array, position = litval.toPosition())
|
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
|
||||||
}
|
|
||||||
litval.structliteral()!=null -> {
|
|
||||||
val values = litval.structliteral().expression().map { it.toAst() }
|
|
||||||
StructLiteralValue(values, litval.toPosition())
|
|
||||||
}
|
}
|
||||||
else -> throw FatalAstException("invalid parsed literal")
|
else -> throw FatalAstException("invalid parsed literal")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(register()!=null)
|
|
||||||
return RegisterExpr(register().toAst(), register().toPosition())
|
|
||||||
|
|
||||||
if(scoped_identifier()!=null)
|
if(scoped_identifier()!=null)
|
||||||
return scoped_identifier().toAst()
|
return scoped_identifier().toAst()
|
||||||
|
|
||||||
@ -470,7 +505,8 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
|||||||
if(funcall!=null) return funcall
|
if(funcall!=null) return funcall
|
||||||
|
|
||||||
if (rangefrom!=null && rangeto!=null) {
|
if (rangefrom!=null && rangeto!=null) {
|
||||||
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, 1, toPosition())
|
val defaultstep = if(rto.text == "to") 1 else -1
|
||||||
|
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
|
||||||
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -492,6 +528,8 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
|||||||
throw FatalAstException(text)
|
throw FatalAstException(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.StringliteralContext.toAst(): StringLiteralValue =
|
||||||
|
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
|
||||||
|
|
||||||
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
|
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
|
||||||
return ArrayIndexedExpression(scoped_identifier().toAst(),
|
return ArrayIndexedExpression(scoped_identifier().toAst(),
|
||||||
@ -499,28 +537,22 @@ private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
|
|||||||
toPosition())
|
toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
|
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
|
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
|
||||||
IdentifierReference(listOf(text), toPosition())
|
IdentifierReference(listOf(text), toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
|
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
|
||||||
IdentifierReference(NAME().map { it.text }, toPosition())
|
IdentifierReference(NAME().map { it.text }, toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
|
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
|
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
|
||||||
"true" -> true
|
"true" -> true
|
||||||
"false" -> false
|
"false" -> false
|
||||||
else -> throw FatalAstException(text)
|
else -> throw FatalAstException(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
|
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
|
||||||
expression().map { it.toAst() }.toTypedArray()
|
expression().map { it.toAst() }.toTypedArray()
|
||||||
|
|
||||||
@ -538,7 +570,6 @@ private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
|
|||||||
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
||||||
val branchcondition = branchcondition().toAst()
|
val branchcondition = branchcondition().toAst()
|
||||||
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
@ -551,25 +582,21 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
|||||||
|
|
||||||
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
|
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
||||||
val loopregister = register()?.toAst()
|
val loopvar = identifier().toAst()
|
||||||
val loopvar = identifier()?.toAst()
|
|
||||||
val iterable = expression()!!.toAst()
|
val iterable = expression()!!.toAst()
|
||||||
val scope =
|
val scope =
|
||||||
if(statement()!=null)
|
if(statement()!=null)
|
||||||
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
||||||
else
|
else
|
||||||
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
||||||
return ForLoop(loopregister, loopvar, iterable, scope, toPosition())
|
return ForLoop(loopvar, iterable, scope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
|
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
|
||||||
|
|
||||||
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
|
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
||||||
val condition = expression().toAst()
|
val condition = expression().toAst()
|
||||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
@ -578,13 +605,20 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
|||||||
return WhileLoop(condition, scope, toPosition())
|
return WhileLoop(condition, scope, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
||||||
|
val iterations = expression()?.toAst()
|
||||||
|
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
|
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||||
|
?: statement().toPosition())
|
||||||
|
return RepeatLoop(iterations, scope, toPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun prog8Parser.UntilloopContext.toAst(): UntilLoop {
|
||||||
val untilCondition = expression().toAst()
|
val untilCondition = expression().toAst()
|
||||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||||
?: statement().toPosition())
|
?: statement().toPosition())
|
||||||
return RepeatLoop(scope, untilCondition, toPosition())
|
return UntilLoop(scope, untilCondition, toPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
|
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
|
||||||
@ -643,4 +677,3 @@ internal fun unescape(str: String, position: Position): String {
|
|||||||
}
|
}
|
||||||
return result.joinToString("")
|
return result.joinToString("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ enum class DataType {
|
|||||||
* is the type assignable to the given other type?
|
* is the type assignable to the given other type?
|
||||||
*/
|
*/
|
||||||
infix fun isAssignableTo(targetType: DataType) =
|
infix fun isAssignableTo(targetType: DataType) =
|
||||||
// what types are assignable to others without loss of precision?
|
// what types are assignable to others, perhaps via a typecast, without loss of precision?
|
||||||
when(this) {
|
when(this) {
|
||||||
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||||
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||||
@ -64,7 +64,7 @@ enum class DataType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Register {
|
enum class CpuRegister {
|
||||||
A,
|
A,
|
||||||
X,
|
X,
|
||||||
Y
|
Y
|
||||||
@ -76,14 +76,23 @@ enum class RegisterOrPair {
|
|||||||
Y,
|
Y,
|
||||||
AX,
|
AX,
|
||||||
AY,
|
AY,
|
||||||
XY
|
XY;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val names by lazy { values().map { it.toString()} }
|
||||||
|
}
|
||||||
|
|
||||||
} // only used in parameter and return value specs in asm subroutines
|
} // only used in parameter and return value specs in asm subroutines
|
||||||
|
|
||||||
enum class Statusflag {
|
enum class Statusflag {
|
||||||
Pc,
|
Pc,
|
||||||
Pz,
|
Pz,
|
||||||
Pv,
|
Pv,
|
||||||
Pn
|
Pn;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val names by lazy { values().map { it.toString()} }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class BranchCondition {
|
enum class BranchCondition {
|
||||||
@ -126,6 +135,13 @@ val ArrayElementTypes = mapOf(
|
|||||||
DataType.ARRAY_W to DataType.WORD,
|
DataType.ARRAY_W to DataType.WORD,
|
||||||
DataType.ARRAY_UW to DataType.UWORD,
|
DataType.ARRAY_UW to DataType.UWORD,
|
||||||
DataType.ARRAY_F to DataType.FLOAT)
|
DataType.ARRAY_F to DataType.FLOAT)
|
||||||
|
val ElementArrayTypes = mapOf(
|
||||||
|
DataType.BYTE to DataType.ARRAY_B,
|
||||||
|
DataType.UBYTE to DataType.ARRAY_UB,
|
||||||
|
DataType.WORD to DataType.ARRAY_W,
|
||||||
|
DataType.UWORD to DataType.ARRAY_UW,
|
||||||
|
DataType.FLOAT to DataType.ARRAY_F
|
||||||
|
)
|
||||||
|
|
||||||
// find the parent node of a specific type or interface
|
// find the parent node of a specific type or interface
|
||||||
// (useful to figure out in what namespace/block something is defined, etc)
|
// (useful to figure out in what namespace/block something is defined, etc)
|
||||||
@ -143,8 +159,15 @@ object ParentSentinel : Node {
|
|||||||
override val position = Position("<<sentinel>>", 0, 0, 0)
|
override val position = Position("<<sentinel>>", 0, 0, 0)
|
||||||
override var parent: Node = this
|
override var parent: Node = this
|
||||||
override fun linkParents(parent: Node) {}
|
override fun linkParents(parent: Node) {}
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||||
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DUMMY = Position("<dummy>", 0, 0, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,35 +3,42 @@ package prog8.ast.base
|
|||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
|
|
||||||
|
|
||||||
fun printErrors(errors: List<Any>, moduleName: String) {
|
class ErrorReporter {
|
||||||
val reportedMessages = mutableSetOf<String>()
|
private enum class MessageSeverity {
|
||||||
System.err.print("\u001b[91m") // bright red
|
WARNING,
|
||||||
errors.forEach {
|
ERROR
|
||||||
val msg = it.toString()
|
}
|
||||||
if(msg !in reportedMessages) {
|
private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position)
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
fun handle() {
|
||||||
|
var numErrors = 0
|
||||||
|
var numWarnings = 0
|
||||||
|
messages.forEach {
|
||||||
|
when(it.severity) {
|
||||||
|
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
|
||||||
|
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
|
||||||
|
}
|
||||||
|
val msg = "${it.position} ${it.severity} ${it.message}".trim()
|
||||||
|
if(msg !in alreadyReportedMessages) {
|
||||||
System.err.println(msg)
|
System.err.println(msg)
|
||||||
reportedMessages.add(msg)
|
alreadyReportedMessages.add(msg)
|
||||||
|
when(it.severity) {
|
||||||
|
MessageSeverity.WARNING -> numWarnings++
|
||||||
|
MessageSeverity.ERROR -> numErrors++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.err.print("\u001b[0m") // reset color
|
System.err.print("\u001b[0m") // reset color
|
||||||
if(reportedMessages.isNotEmpty())
|
}
|
||||||
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
|
messages.clear()
|
||||||
}
|
if(numErrors>0)
|
||||||
|
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
|
||||||
|
}
|
||||||
fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
|
|
||||||
print("\u001b[93m") // bright yellow
|
fun isEmpty() = messages.isEmpty()
|
||||||
print("$position Warning: $msg")
|
|
||||||
if(detailInfo==null)
|
|
||||||
print("\n")
|
|
||||||
else
|
|
||||||
println(": $detailInfo\n")
|
|
||||||
print("\u001b[0m") // normal
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun printWarning(msg: String) {
|
|
||||||
print("\u001b[93m") // bright yellow
|
|
||||||
print("Warning: $msg")
|
|
||||||
print("\u001b[0m\n") // normal
|
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,17 @@ package prog8.ast.base
|
|||||||
|
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
|
|
||||||
class FatalAstException (override var message: String) : Exception(message)
|
open class FatalAstException (override var message: String) : Exception(message)
|
||||||
|
|
||||||
open class AstException (override var message: String) : Exception(message)
|
open class AstException (override var message: String) : Exception(message)
|
||||||
|
|
||||||
class SyntaxError(override var message: String, val position: Position) : AstException(message) {
|
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
|
||||||
override fun toString() = "$position Syntax error: $message"
|
override fun toString() = "$position Syntax error: $message"
|
||||||
}
|
}
|
||||||
|
|
||||||
open class NameError(override var message: String, val position: Position) : AstException(message) {
|
|
||||||
override fun toString() = "$position Name error: $message"
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
||||||
override fun toString() = "$position Error: $message"
|
override fun toString() = "$position Error: $message"
|
||||||
}
|
}
|
||||||
|
|
||||||
class UndefinedSymbolError(symbol: IdentifierReference)
|
class UndefinedSymbolError(symbol: IdentifierReference)
|
||||||
: NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
|
||||||
|
@ -4,66 +4,79 @@ import prog8.ast.Module
|
|||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.processing.*
|
import prog8.ast.processing.*
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
|
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||||
|
import prog8.optimizer.AssignmentTransformer
|
||||||
|
|
||||||
|
|
||||||
// the name of the subroutine that should be called for every block to initialize its variables
|
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
|
||||||
internal const val initvarsSubName="prog8_init_vars"
|
val checker = AstChecker(this, compilerOptions, errors)
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.removeNopsFlattenAnonScopes() {
|
|
||||||
val flattener = FlattenAnonymousScopesAndRemoveNops()
|
|
||||||
flattener.visit(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
|
|
||||||
val checker = AstChecker(this, compilerOptions)
|
|
||||||
checker.visit(this)
|
checker.visit(this)
|
||||||
printErrors(checker.result(), name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
|
||||||
internal fun Program.anonscopeVarsCleanup() {
|
val fixer = BeforeAsmGenerationAstChanger(this, errors)
|
||||||
val mover = AnonymousScopeVarsCleanup(this)
|
fixer.visit(this)
|
||||||
mover.visit(this)
|
fixer.applyModifications()
|
||||||
printErrors(mover.result(), name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.reorderStatements() {
|
internal fun Program.reorderStatements() {
|
||||||
val initvalueCreator = VarInitValueAndAddressOfCreator(this)
|
val reorder = StatementReorderer(this)
|
||||||
initvalueCreator.visit(this)
|
reorder.visit(this)
|
||||||
|
reorder.applyModifications()
|
||||||
val checker = StatementReorderer(this)
|
|
||||||
checker.visit(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.addTypecasts() {
|
internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||||
val caster = TypecastsAdder(this)
|
val caster = TypecastsAdder(this, errors)
|
||||||
caster.visit(this)
|
caster.visit(this)
|
||||||
|
caster.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.verifyFunctionArgTypes() {
|
||||||
|
val fixer = VerifyFunctionArgTypes(this)
|
||||||
|
fixer.visit(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.transformAssignments(errors: ErrorReporter) {
|
||||||
|
val transform = AssignmentTransformer(this, errors)
|
||||||
|
transform.visit(this)
|
||||||
|
while(transform.optimizationsDone>0 && errors.isEmpty()) {
|
||||||
|
transform.applyModifications()
|
||||||
|
transform.optimizationsDone = 0
|
||||||
|
transform.visit(this)
|
||||||
|
}
|
||||||
|
transform.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Module.checkImportedValid() {
|
internal fun Module.checkImportedValid() {
|
||||||
val checker = ImportedModuleDirectiveRemover()
|
val imr = ImportedModuleDirectiveRemover()
|
||||||
checker.visit(this)
|
imr.visit(this, this.parent)
|
||||||
printErrors(checker.result(), name)
|
imr.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.checkRecursion() {
|
internal fun Program.checkRecursion(errors: ErrorReporter) {
|
||||||
val checker = AstRecursionChecker(namespace)
|
val checker = AstRecursionChecker(namespace, errors)
|
||||||
checker.visit(this)
|
checker.visit(this)
|
||||||
printErrors(checker.result(), name)
|
checker.processMessages(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||||
|
|
||||||
internal fun Program.checkIdentifiers() {
|
val checker2 = AstIdentifiersChecker(this, errors)
|
||||||
val checker = AstIdentifiersChecker(this)
|
checker2.visit(this)
|
||||||
checker.visit(this)
|
|
||||||
|
|
||||||
if(modules.map {it.name}.toSet().size != modules.size) {
|
if(errors.isEmpty()) {
|
||||||
throw FatalAstException("modules should all be unique")
|
val transforms = AstVariousTransforms(this)
|
||||||
|
transforms.visit(this)
|
||||||
|
transforms.applyModifications()
|
||||||
}
|
}
|
||||||
|
|
||||||
printErrors(checker.result(), name)
|
if (modules.map { it.name }.toSet().size != modules.size) {
|
||||||
|
throw FatalAstException("modules should all be unique")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Program.variousCleanups() {
|
||||||
|
val process = VariousCleanups()
|
||||||
|
process.visit(this)
|
||||||
|
process.applyModifications()
|
||||||
}
|
}
|
||||||
|
@ -3,17 +3,15 @@ package prog8.ast.expressions
|
|||||||
import prog8.ast.*
|
import prog8.ast.*
|
||||||
import prog8.ast.antlr.escape
|
import prog8.ast.antlr.escape
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
import prog8.ast.processing.AstWalker
|
||||||
import prog8.ast.processing.IAstVisitor
|
import prog8.ast.processing.IAstVisitor
|
||||||
import prog8.ast.statements.ArrayIndex
|
import prog8.ast.statements.*
|
||||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.compiler.target.CompilationTarget
|
import prog8.compiler.target.CompilationTarget
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
|
import prog8.functions.CannotEvaluateException
|
||||||
import prog8.functions.NotConstArgumentException
|
import prog8.functions.NotConstArgumentException
|
||||||
import prog8.functions.builtinFunctionReturnType
|
import prog8.functions.builtinFunctionReturnType
|
||||||
import java.util.Objects
|
import java.util.*
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
|
||||||
@ -22,30 +20,28 @@ val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "=
|
|||||||
|
|
||||||
sealed class Expression: Node {
|
sealed class Expression: Node {
|
||||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||||
abstract fun accept(visitor: IAstModifyingVisitor): Expression
|
|
||||||
abstract fun accept(visitor: IAstVisitor)
|
abstract fun accept(visitor: IAstVisitor)
|
||||||
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
|
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||||
|
abstract fun referencesIdentifiers(vararg name: String): Boolean
|
||||||
abstract fun inferType(program: Program): InferredTypes.InferredType
|
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||||
|
|
||||||
infix fun isSameAs(other: Expression): Boolean {
|
infix fun isSameAs(other: Expression): Boolean {
|
||||||
if(this===other)
|
if(this===other)
|
||||||
return true
|
return true
|
||||||
when(this) {
|
return when(this) {
|
||||||
is RegisterExpr ->
|
|
||||||
return (other is RegisterExpr && other.register==register)
|
|
||||||
is IdentifierReference ->
|
is IdentifierReference ->
|
||||||
return (other is IdentifierReference && other.nameInSource==nameInSource)
|
(other is IdentifierReference && other.nameInSource==nameInSource)
|
||||||
is PrefixExpression ->
|
is PrefixExpression ->
|
||||||
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
(other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
||||||
is BinaryExpression ->
|
is BinaryExpression ->
|
||||||
return (other is BinaryExpression && other.operator==operator
|
(other is BinaryExpression && other.operator==operator
|
||||||
&& other.left isSameAs left
|
&& other.left isSameAs left
|
||||||
&& other.right isSameAs right)
|
&& other.right isSameAs right)
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
||||||
&& other.arrayspec.index isSameAs arrayspec.index)
|
&& other.arrayspec.index isSameAs arrayspec.index)
|
||||||
}
|
}
|
||||||
else -> return other==this
|
else -> other==this
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,9 +55,16 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
|||||||
expression.linkParents(this)
|
expression.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(node === expression && replacement is Expression)
|
||||||
|
expression = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
val inferred = expression.inferType(program)
|
val inferred = expression.inferType(program)
|
||||||
@ -99,6 +102,16 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
|||||||
right.linkParents(this)
|
right.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression)
|
||||||
|
when {
|
||||||
|
node===left -> left = replacement
|
||||||
|
node===right -> right = replacement
|
||||||
|
else -> throw FatalAstException("invalid replace, no child $node")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "[$left $operator $right]"
|
return "[$left $operator $right]"
|
||||||
}
|
}
|
||||||
@ -106,8 +119,9 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
|||||||
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
|
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
val leftDt = left.inferType(program)
|
val leftDt = left.inferType(program)
|
||||||
@ -208,9 +222,19 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
|||||||
arrayspec.linkParents(this)
|
arrayspec.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===identifier -> identifier = replacement as IdentifierReference
|
||||||
|
node===arrayspec.index -> arrayspec.index = replacement as Expression
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
|
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
|
||||||
|
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
@ -238,8 +262,15 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
|||||||
expression.linkParents(this)
|
expression.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression && node===expression)
|
||||||
|
expression = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
||||||
override fun constValue(program: Program): NumericLiteralValue? {
|
override fun constValue(program: Program): NumericLiteralValue? {
|
||||||
@ -262,11 +293,17 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
|||||||
identifier.parent=this
|
identifier.parent=this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is IdentifierReference && node===identifier)
|
||||||
|
identifier = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun referencesIdentifiers(vararg name: String) = false
|
override fun referencesIdentifiers(vararg name: String) = false
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression(), IAssignable {
|
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression(), IAssignable {
|
||||||
@ -277,8 +314,15 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
|||||||
this.addressExpression.linkParents(this)
|
this.addressExpression.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression && node===addressExpression)
|
||||||
|
addressExpression = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = false
|
override fun referencesIdentifiers(vararg name: String) = false
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
@ -328,11 +372,15 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
|||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace here")
|
||||||
|
}
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = false
|
override fun referencesIdentifiers(vararg name: String) = false
|
||||||
override fun constValue(program: Program) = this
|
override fun constValue(program: Program) = this
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
|
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
|
||||||
|
|
||||||
@ -407,29 +455,10 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StructLiteralValue(var values: List<Expression>,
|
|
||||||
override val position: Position): Expression() {
|
|
||||||
override lateinit var parent: Node
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
|
||||||
this.parent=parent
|
|
||||||
values.forEach { it.linkParents(this) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
|
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return "struct{ ${values.joinToString(", ")} }"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
|
private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
|
||||||
|
|
||||||
class StringLiteralValue(val value: String,
|
class StringLiteralValue(val value: String,
|
||||||
|
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
|
||||||
override val position: Position) : Expression() {
|
override val position: Position) : Expression() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
@ -438,22 +467,28 @@ class StringLiteralValue(val value: String,
|
|||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace here")
|
||||||
|
}
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = false
|
override fun referencesIdentifiers(vararg name: String) = false
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String = "'${escape(value)}'"
|
override fun toString(): String = "'${escape(value)}'"
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STR)
|
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STR)
|
||||||
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
|
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
|
||||||
override fun hashCode(): Int = value.hashCode()
|
override fun hashCode(): Int = Objects.hash(value, altEncoding)
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if(other==null || other !is StringLiteralValue)
|
if(other==null || other !is StringLiteralValue)
|
||||||
return false
|
return false
|
||||||
return value==other.value
|
return value==other.value && altEncoding == other.altEncoding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ArrayLiteralValue(val type: DataType, // only array types
|
class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred because not all array literals hava a known type yet
|
||||||
val value: Array<Expression>,
|
val value: Array<Expression>,
|
||||||
override val position: Position) : Expression() {
|
override val position: Position) : Expression() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
@ -464,12 +499,22 @@ class ArrayLiteralValue(val type: DataType, // only array types
|
|||||||
this.parent = parent
|
this.parent = parent
|
||||||
value.forEach {it.linkParents(this)}
|
value.forEach {it.linkParents(this)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression)
|
||||||
|
val idx = value.indexOfFirst { it===node }
|
||||||
|
value[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String = "$value"
|
override fun toString(): String = "$value"
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) type else guessDatatype(program)
|
||||||
|
|
||||||
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
|
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
|
||||||
override fun hashCode(): Int = Objects.hash(value, type)
|
override fun hashCode(): Int = Objects.hash(value, type)
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@ -478,8 +523,36 @@ class ArrayLiteralValue(val type: DataType, // only array types
|
|||||||
return type==other.type && value.contentEquals(other.value)
|
return type==other.type && value.contentEquals(other.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
val forloop = parent as? ForLoop
|
||||||
|
if(forloop != null) {
|
||||||
|
val loopvarDt = forloop.loopVarDt(program)
|
||||||
|
if(loopvarDt.isKnown) {
|
||||||
|
return if(loopvarDt.typeOrElse(DataType.STRUCT) !in ElementArrayTypes)
|
||||||
|
InferredTypes.InferredType.unknown()
|
||||||
|
else
|
||||||
|
InferredTypes.InferredType.known(ElementArrayTypes.getValue(loopvarDt.typeOrElse(DataType.STRUCT)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, select the "biggegst" datatype based on the elements in the array.
|
||||||
|
val datatypesInArray = value.map { it.inferType(program) }
|
||||||
|
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
|
||||||
|
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
|
||||||
|
return when {
|
||||||
|
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
|
||||||
|
DataType.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
|
||||||
|
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||||
|
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
|
||||||
|
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB)
|
||||||
|
else -> InferredTypes.InferredType.unknown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun cast(targettype: DataType): ArrayLiteralValue? {
|
fun cast(targettype: DataType): ArrayLiteralValue? {
|
||||||
if(type==targettype)
|
if(type.istype(targettype))
|
||||||
return this
|
return this
|
||||||
if(targettype in ArrayDatatypes) {
|
if(targettype in ArrayDatatypes) {
|
||||||
val elementType = ArrayElementTypes.getValue(targettype)
|
val elementType = ArrayElementTypes.getValue(targettype)
|
||||||
@ -498,7 +571,7 @@ class ArrayLiteralValue(val type: DataType, // only array types
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
return ArrayLiteralValue(targettype, castArray, position = position)
|
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
|
||||||
}
|
}
|
||||||
return null // invalid type conversion from $this to $targettype
|
return null // invalid type conversion from $this to $targettype
|
||||||
}
|
}
|
||||||
@ -517,9 +590,21 @@ class RangeExpr(var from: Expression,
|
|||||||
step.linkParents(this)
|
step.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression)
|
||||||
|
when {
|
||||||
|
from===node -> from=replacement
|
||||||
|
to===node -> to=replacement
|
||||||
|
step===node -> step=replacement
|
||||||
|
else -> throw FatalAstException("invalid replacement")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
|
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
val fromDt=from.inferType(program)
|
val fromDt=from.inferType(program)
|
||||||
@ -552,9 +637,9 @@ class RangeExpr(var from: Expression,
|
|||||||
val fromString = from as? StringLiteralValue
|
val fromString = from as? StringLiteralValue
|
||||||
val toString = to as? StringLiteralValue
|
val toString = to as? StringLiteralValue
|
||||||
if(fromString!=null && toString!=null ) {
|
if(fromString!=null && toString!=null ) {
|
||||||
// string range -> int range over petscii values
|
// string range -> int range over character values
|
||||||
fromVal = CompilationTarget.encodeString(fromString.value)[0].toInt()
|
fromVal = CompilationTarget.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
|
||||||
toVal = CompilationTarget.encodeString(toString.value)[0].toInt()
|
toVal = CompilationTarget.encodeString(toString.value, fromString.altEncoding)[0].toInt()
|
||||||
} else {
|
} else {
|
||||||
val fromLv = from as? NumericLiteralValue
|
val fromLv = from as? NumericLiteralValue
|
||||||
val toLv = to as? NumericLiteralValue
|
val toLv = to as? NumericLiteralValue
|
||||||
@ -584,24 +669,6 @@ internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RegisterExpr(val register: Register, override val position: Position) : Expression(), IAssignable {
|
|
||||||
override lateinit var parent: Node
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
|
||||||
this.parent = parent
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
|
||||||
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
|
|
||||||
override fun toString(): String {
|
|
||||||
return "RegisterExpr(register=$register, pos=$position)"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
|
||||||
}
|
|
||||||
|
|
||||||
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
|
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
@ -614,10 +681,17 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
|||||||
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
|
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
|
||||||
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
|
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
|
||||||
|
|
||||||
|
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
|
||||||
|
override fun hashCode() = nameInSource.hashCode()
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace here")
|
||||||
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program): NumericLiteralValue? {
|
override fun constValue(program: Program): NumericLiteralValue? {
|
||||||
val node = program.namespace.lookup(nameInSource, this)
|
val node = program.namespace.lookup(nameInSource, this)
|
||||||
?: throw UndefinedSymbolError(this)
|
?: throw UndefinedSymbolError(this)
|
||||||
@ -634,16 +708,17 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
|||||||
return "IdentifierRef($nameInSource)"
|
return "IdentifierRef($nameInSource)"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
|
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
|
||||||
|
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
val targetStmt = targetStatement(program.namespace)
|
val targetStmt = targetStatement(program.namespace)
|
||||||
if(targetStmt is VarDecl) {
|
return if(targetStmt is VarDecl) {
|
||||||
return InferredTypes.knownFor(targetStmt.datatype)
|
InferredTypes.knownFor(targetStmt.datatype)
|
||||||
} else {
|
} else {
|
||||||
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
|
InferredTypes.InferredType.unknown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,6 +747,16 @@ class FunctionCall(override var target: IdentifierReference,
|
|||||||
args.forEach { it.linkParents(this) }
|
args.forEach { it.linkParents(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
if(node===target)
|
||||||
|
target=replacement as IdentifierReference
|
||||||
|
else {
|
||||||
|
val idx = args.indexOfFirst { it===node }
|
||||||
|
args[idx] = replacement as Expression
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun constValue(program: Program) = constValue(program, true)
|
override fun constValue(program: Program) = constValue(program, true)
|
||||||
|
|
||||||
private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteralValue? {
|
private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteralValue? {
|
||||||
@ -702,14 +787,19 @@ class FunctionCall(override var target: IdentifierReference,
|
|||||||
// const-evaluating the builtin function call failed.
|
// const-evaluating the builtin function call failed.
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
catch(x: CannotEvaluateException) {
|
||||||
|
// const-evaluating the builtin function call failed.
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "FunctionCall(target=$target, pos=$position)"
|
return "FunctionCall(target=$target, pos=$position)"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || args.any{it.referencesIdentifiers(*name)}
|
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || args.any{it.referencesIdentifiers(*name)}
|
||||||
|
|
||||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package prog8.ast.expressions
|
package prog8.ast.expressions
|
||||||
|
|
||||||
import java.util.Objects
|
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
object InferredTypes {
|
object InferredTypes {
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
package prog8.ast.processing
|
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.AstException
|
|
||||||
import prog8.ast.base.NameError
|
|
||||||
import prog8.ast.statements.AnonymousScope
|
|
||||||
import prog8.ast.statements.Statement
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
|
|
||||||
class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
|
|
||||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
|
||||||
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
|
|
||||||
|
|
||||||
fun result(): List<AstException> {
|
|
||||||
return checkResult
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
|
||||||
varsToMove.clear()
|
|
||||||
super.visit(program)
|
|
||||||
for((scope, decls) in varsToMove) {
|
|
||||||
val sub = scope.definingSubroutine()!!
|
|
||||||
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
|
||||||
var conflicts = false
|
|
||||||
decls.forEach {
|
|
||||||
val existing = existingVariables[it.name]
|
|
||||||
if (existing!=null) {
|
|
||||||
checkResult.add(NameError("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position))
|
|
||||||
conflicts = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!conflicts) {
|
|
||||||
decls.forEach { scope.remove(it) }
|
|
||||||
sub.statements.addAll(0, decls)
|
|
||||||
decls.forEach { it.parent = sub }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(scope: AnonymousScope): Statement {
|
|
||||||
val scope2 = super.visit(scope) as AnonymousScope
|
|
||||||
val vardecls = scope2.statements.filterIsInstance<VarDecl>()
|
|
||||||
varsToMove[scope2] = vardecls
|
|
||||||
return scope2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,6 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
@ -10,104 +8,74 @@ import prog8.ast.statements.*
|
|||||||
import prog8.compiler.target.CompilationTarget
|
import prog8.compiler.target.CompilationTarget
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
|
||||||
internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor {
|
|
||||||
|
|
||||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
|
||||||
|
|
||||||
private var blocks = mutableMapOf<String, Block>()
|
private var blocks = mutableMapOf<String, Block>()
|
||||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
|
||||||
|
|
||||||
internal fun result(): List<AstException> {
|
|
||||||
return checkResult
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun nameError(name: String, position: Position, existing: Statement) {
|
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||||
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
|
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(module: Module) {
|
override fun visit(module: Module) {
|
||||||
vardeclsToAdd.clear()
|
|
||||||
blocks.clear() // blocks may be redefined within a different module
|
blocks.clear() // blocks may be redefined within a different module
|
||||||
|
|
||||||
super.visit(module)
|
super.visit(module)
|
||||||
// add any new vardecls to the various scopes
|
|
||||||
for((where, decls) in vardeclsToAdd) {
|
|
||||||
where.statements.addAll(0, decls)
|
|
||||||
decls.forEach { it.linkParents(where as Node) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(block: Block): Statement {
|
override fun visit(block: Block) {
|
||||||
val existing = blocks[block.name]
|
val existing = blocks[block.name]
|
||||||
if(existing!=null)
|
if(existing!=null)
|
||||||
nameError(block.name, block.position, existing)
|
nameError(block.name, block.position, existing)
|
||||||
else
|
else
|
||||||
blocks[block.name] = block
|
blocks[block.name] = block
|
||||||
|
|
||||||
return super.visit(block)
|
super.visit(block)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): Expression {
|
override fun visit(decl: VarDecl) {
|
||||||
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
|
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||||
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
|
|
||||||
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
|
|
||||||
typecast.linkParents(functionCall.parent)
|
|
||||||
return super.visit(typecast)
|
|
||||||
}
|
|
||||||
return super.visit(functionCall)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): Statement {
|
|
||||||
// first, check if there are datatype errors on the vardecl
|
|
||||||
decl.datatypeErrors.forEach { checkResult.add(it) }
|
|
||||||
|
|
||||||
// now check the identifier
|
|
||||||
if(decl.name in BuiltinFunctions)
|
if(decl.name in BuiltinFunctions)
|
||||||
// the builtin functions can't be redefined
|
errors.err("builtin function cannot be redefined", decl.position)
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
|
|
||||||
|
|
||||||
if(decl.name in CompilationTarget.machine.opcodeNames)
|
if(decl.name in CompilationTarget.machine.opcodeNames)
|
||||||
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position))
|
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
||||||
|
|
||||||
// is it a struct variable? then define all its struct members as mangled names,
|
|
||||||
// and include the original decl as well.
|
|
||||||
if(decl.datatype==DataType.STRUCT) {
|
if(decl.datatype==DataType.STRUCT) {
|
||||||
if(decl.structHasBeenFlattened)
|
if (decl.structHasBeenFlattened)
|
||||||
return super.visit(decl) // don't do this multiple times
|
return super.visit(decl) // don't do this multiple times
|
||||||
|
|
||||||
if(decl.struct==null) {
|
if (decl.struct == null) {
|
||||||
checkResult.add(NameError("undefined struct type", decl.position))
|
errors.err("undefined struct type", decl.position)
|
||||||
return super.visit(decl)
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
|
if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes })
|
||||||
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
|
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
|
||||||
|
|
||||||
if(decl.value is NumericLiteralValue) {
|
if (decl.value is NumericLiteralValue) {
|
||||||
checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position))
|
errors.err("you cannot initialize a struct using a single value", decl.position)
|
||||||
return super.visit(decl)
|
return super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
val decls = decl.flattenStructMembers()
|
if (decl.value != null && decl.value !is ArrayLiteralValue) {
|
||||||
decls.add(decl)
|
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
|
||||||
val result = AnonymousScope(decls, decl.position)
|
return super.visit(decl)
|
||||||
result.linkParents(decl.parent)
|
}
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val existing = program.namespace.lookup(listOf(decl.name), decl)
|
val existing = program.namespace.lookup(listOf(decl.name), decl)
|
||||||
if (existing != null && existing !== decl)
|
if (existing != null && existing !== decl)
|
||||||
nameError(decl.name, decl.position, existing)
|
nameError(decl.name, decl.position, existing)
|
||||||
|
|
||||||
return super.visit(decl)
|
super.visit(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine): Statement {
|
override fun visit(subroutine: Subroutine) {
|
||||||
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
|
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
|
||||||
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position))
|
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
||||||
} else if(subroutine.name in BuiltinFunctions) {
|
} else if(subroutine.name in BuiltinFunctions) {
|
||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
|
errors.err("builtin function cannot be redefined", subroutine.position)
|
||||||
} else {
|
} else {
|
||||||
// already reported elsewhere:
|
// already reported elsewhere:
|
||||||
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
||||||
@ -139,257 +107,50 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
|
|||||||
nameError(name, sub.position, subroutine)
|
nameError(name, sub.position, subroutine)
|
||||||
}
|
}
|
||||||
|
|
||||||
// inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters)
|
|
||||||
// NOTE:
|
|
||||||
// - numeric types BYTE and WORD and FLOAT are passed by value;
|
|
||||||
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
|
|
||||||
if(subroutine.asmAddress==null) {
|
|
||||||
if(subroutine.asmParameterRegisters.isEmpty()) {
|
|
||||||
subroutine.parameters
|
|
||||||
.filter { it.name !in namesInSub }
|
|
||||||
.forEach {
|
|
||||||
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.NOT_IN_ZEROPAGE, null, it.name, null, null,
|
|
||||||
isArray = false, autogeneratedDontRemove = true, position = subroutine.position)
|
|
||||||
vardecl.linkParents(subroutine)
|
|
||||||
subroutine.statements.add(0, vardecl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||||
checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position))
|
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return super.visit(subroutine)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(label: Label): Statement {
|
super.visit(subroutine)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visit(label: Label) {
|
||||||
if(label.name in CompilationTarget.machine.opcodeNames)
|
if(label.name in CompilationTarget.machine.opcodeNames)
|
||||||
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${label.name}'", label.position))
|
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
||||||
|
|
||||||
if(label.name in BuiltinFunctions) {
|
if(label.name in BuiltinFunctions) {
|
||||||
// the builtin functions can't be redefined
|
// the builtin functions can't be redefined
|
||||||
checkResult.add(NameError("builtin function cannot be redefined", label.position))
|
errors.err("builtin function cannot be redefined", label.position)
|
||||||
} else {
|
} else {
|
||||||
val existing = program.namespace.lookup(listOf(label.name), label)
|
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
|
||||||
if (existing != null && existing !== label)
|
for(el in existing) {
|
||||||
nameError(label.name, label.position, existing)
|
if(el === label || el.name != label.name)
|
||||||
}
|
continue
|
||||||
return super.visit(label)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(forLoop: ForLoop): Statement {
|
|
||||||
// If the for loop has a decltype, it means to declare the loopvar inside the loop body
|
|
||||||
// rather than reusing an already declared loopvar from an outer scope.
|
|
||||||
// For loops that loop over an interable variable (instead of a range of numbers) get an
|
|
||||||
// additional interation count variable in their scope.
|
|
||||||
if(forLoop.loopRegister!=null) {
|
|
||||||
if(forLoop.loopRegister == Register.X)
|
|
||||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
|
||||||
} else {
|
|
||||||
val loopVar = forLoop.loopVar
|
|
||||||
if (loopVar != null) {
|
|
||||||
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
|
|
||||||
val loopvarName = "prog8_loopvar_$validName"
|
|
||||||
if (forLoop.iterable !is RangeExpr) {
|
|
||||||
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first())
|
|
||||||
if (existing == null) {
|
|
||||||
// create loop iteration counter variable (without value, to avoid an assignment)
|
|
||||||
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null,
|
|
||||||
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
|
|
||||||
vardecl.linkParents(forLoop.body)
|
|
||||||
forLoop.body.statements.add(0, vardecl)
|
|
||||||
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(forLoop)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(assignTarget: AssignTarget): AssignTarget {
|
|
||||||
if(assignTarget.register== Register.X)
|
|
||||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
|
|
||||||
return super.visit(assignTarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(returnStmt: Return): Statement {
|
|
||||||
if(returnStmt.value!=null) {
|
|
||||||
// possibly adjust any literal values returned, into the desired returning data type
|
|
||||||
val subroutine = returnStmt.definingSubroutine()!!
|
|
||||||
if(subroutine.returntypes.size!=1)
|
|
||||||
return returnStmt // mismatch in number of return values, error will be printed later.
|
|
||||||
val lval = returnStmt.value as? NumericLiteralValue
|
|
||||||
returnStmt.value = lval?.cast(subroutine.returntypes.single()) ?: returnStmt.value!!
|
|
||||||
}
|
|
||||||
return super.visit(returnStmt)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
|
||||||
val array = super.visit(arrayLiteral)
|
|
||||||
if(array is ArrayLiteralValue) {
|
|
||||||
val vardecl = array.parent as? VarDecl
|
|
||||||
return if(vardecl!=null)
|
|
||||||
fixupArrayEltDatatypesFromVardecl(array, vardecl)
|
|
||||||
else {
|
else {
|
||||||
// fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
|
nameError(label.name, label.position, el)
|
||||||
// (we don't know the desired datatype here exactly so we guess)
|
break
|
||||||
val datatype = determineArrayDt(array.value)
|
|
||||||
val litval2 = array.cast(datatype)
|
|
||||||
if(litval2!=null) {
|
|
||||||
litval2.parent = array.parent
|
|
||||||
// finally, replace the literal array by a identifier reference.
|
|
||||||
makeIdentifierFromRefLv(litval2)
|
|
||||||
} else array
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return array
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(stringLiteral: StringLiteralValue): Expression {
|
super.visit(label)
|
||||||
val string = super.visit(stringLiteral)
|
}
|
||||||
if(string is StringLiteralValue) {
|
|
||||||
val vardecl = string.parent as? VarDecl
|
override fun visit(string: StringLiteralValue) {
|
||||||
// intern the string; move it into the heap
|
|
||||||
if (string.value.length !in 1..255)
|
if (string.value.length !in 1..255)
|
||||||
checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position))
|
errors.err("string literal length must be between 1 and 255", string.position)
|
||||||
return if (vardecl != null)
|
|
||||||
string
|
super.visit(string)
|
||||||
else
|
|
||||||
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
|
|
||||||
}
|
|
||||||
return string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun determineArrayDt(array: Array<Expression>): DataType {
|
override fun visit(structDecl: StructDecl) {
|
||||||
val datatypesInArray = array.map { it.inferType(program) }
|
|
||||||
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
|
|
||||||
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
|
|
||||||
return when {
|
|
||||||
DataType.FLOAT in dts -> DataType.ARRAY_F
|
|
||||||
DataType.WORD in dts -> DataType.ARRAY_W
|
|
||||||
DataType.UWORD in dts -> DataType.ARRAY_UW
|
|
||||||
DataType.BYTE in dts -> DataType.ARRAY_B
|
|
||||||
DataType.UBYTE in dts -> DataType.ARRAY_UB
|
|
||||||
else -> throw IllegalArgumentException("can't determine type of array")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
|
|
||||||
// a referencetype literal value that's not declared as a variable
|
|
||||||
// we need to introduce an auto-generated variable for this to be able to refer to the value
|
|
||||||
// note: if the var references the same literal value, it is not yet de-duplicated here.
|
|
||||||
val scope = array.definingScope()
|
|
||||||
val variable = VarDecl.createAuto(array)
|
|
||||||
return replaceWithIdentifier(variable, scope, array.parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
|
|
||||||
// a referencetype literal value that's not declared as a variable
|
|
||||||
// we need to introduce an auto-generated variable for this to be able to refer to the value
|
|
||||||
// note: if the var references the same literal value, it is not yet de-duplicated here.
|
|
||||||
val scope = string.definingScope()
|
|
||||||
val variable = VarDecl.createAuto(string)
|
|
||||||
return replaceWithIdentifier(variable, scope, string.parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
|
|
||||||
val variable1 = addVarDecl(scope, variable)
|
|
||||||
// replace the reference literal by a identifier reference
|
|
||||||
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
|
|
||||||
identifier.parent = parent
|
|
||||||
return identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(structDecl: StructDecl): Statement {
|
|
||||||
for(member in structDecl.statements){
|
for(member in structDecl.statements){
|
||||||
val decl = member as? VarDecl
|
val decl = member as? VarDecl
|
||||||
if(decl!=null && decl.datatype !in NumericDatatypes)
|
if(decl!=null && decl.datatype !in NumericDatatypes)
|
||||||
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
|
errors.err("structs can only contain numerical types", decl.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.visit(structDecl)
|
super.visit(structDecl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(expr: BinaryExpression): Expression {
|
|
||||||
return when {
|
|
||||||
expr.left is StringLiteralValue ->
|
|
||||||
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
|
|
||||||
expr.right is StringLiteralValue ->
|
|
||||||
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
|
|
||||||
else -> super.visit(expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
|
||||||
val constvalue = operand.constValue(program)
|
|
||||||
if(constvalue!=null) {
|
|
||||||
if (expr.operator == "*") {
|
|
||||||
// repeat a string a number of times
|
|
||||||
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(expr.operator == "+" && operand is StringLiteralValue) {
|
|
||||||
// concatenate two strings
|
|
||||||
return StringLiteralValue("${string.value}${operand.value}", expr.position)
|
|
||||||
}
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
|
|
||||||
if(scope !in vardeclsToAdd)
|
|
||||||
vardeclsToAdd[scope] = mutableListOf()
|
|
||||||
val declList = vardeclsToAdd.getValue(scope)
|
|
||||||
val existing = declList.singleOrNull { it.name==variable.name }
|
|
||||||
return if(existing!=null) {
|
|
||||||
existing
|
|
||||||
} else {
|
|
||||||
declList.add(variable)
|
|
||||||
variable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun fixupArrayEltDatatypes(array: ArrayLiteralValue, program: Program): ArrayLiteralValue {
|
|
||||||
val dts = array.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
|
|
||||||
if(dts.any { it !in NumericDatatypes }) {
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
val dt = when {
|
|
||||||
DataType.FLOAT in dts -> DataType.ARRAY_F
|
|
||||||
DataType.WORD in dts -> DataType.ARRAY_W
|
|
||||||
DataType.UWORD in dts -> DataType.ARRAY_UW
|
|
||||||
DataType.BYTE in dts -> DataType.ARRAY_B
|
|
||||||
else -> DataType.ARRAY_UB
|
|
||||||
}
|
|
||||||
if(dt==array.type)
|
|
||||||
return array
|
|
||||||
|
|
||||||
// convert values and array type
|
|
||||||
val elementType = ArrayElementTypes.getValue(dt)
|
|
||||||
val allNumerics = array.value.all { it is NumericLiteralValue }
|
|
||||||
if(allNumerics) {
|
|
||||||
val values = array.value.map { (it as NumericLiteralValue).cast(elementType) as Expression }.toTypedArray()
|
|
||||||
val array2 = ArrayLiteralValue(dt, values, array.position)
|
|
||||||
array2.linkParents(array.parent)
|
|
||||||
return array2
|
|
||||||
}
|
|
||||||
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun fixupArrayEltDatatypesFromVardecl(array: ArrayLiteralValue, vardecl: VarDecl): ArrayLiteralValue {
|
|
||||||
val arrayDt = array.type
|
|
||||||
if(arrayDt!=vardecl.datatype) {
|
|
||||||
// fix the datatype of the array (also on the heap) to match the vardecl
|
|
||||||
val cast = array.cast(vardecl.datatype)
|
|
||||||
if (cast != null) {
|
|
||||||
vardecl.value = cast
|
|
||||||
cast.linkParents(vardecl)
|
|
||||||
return cast
|
|
||||||
}
|
|
||||||
// can't be casted yet, attempt again later
|
|
||||||
}
|
|
||||||
return array
|
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.ErrorReporter
|
||||||
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.FunctionCall
|
import prog8.ast.expressions.FunctionCall
|
||||||
import prog8.ast.statements.FunctionCallStatement
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
import prog8.ast.statements.Subroutine
|
import prog8.ast.statements.Subroutine
|
||||||
|
|
||||||
|
|
||||||
internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisitor {
|
internal class AstRecursionChecker(private val namespace: INameScope,
|
||||||
|
private val errors: ErrorReporter) : IAstVisitor {
|
||||||
private val callGraph = DirectedGraph<INameScope>()
|
private val callGraph = DirectedGraph<INameScope>()
|
||||||
|
|
||||||
internal fun result(): List<AstException> {
|
fun processMessages(modulename: String) {
|
||||||
val cycle = callGraph.checkForCycle()
|
val cycle = callGraph.checkForCycle()
|
||||||
if(cycle.isEmpty())
|
if(cycle.isEmpty())
|
||||||
return emptyList()
|
return
|
||||||
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||||
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain"))
|
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
@ -44,7 +46,6 @@ internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisi
|
|||||||
super.visit(functionCall)
|
super.visit(functionCall)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private class DirectedGraph<VT> {
|
private class DirectedGraph<VT> {
|
||||||
private val graph = mutableMapOf<VT, MutableSet<VT>>()
|
private val graph = mutableMapOf<VT, MutableSet<VT>>()
|
||||||
private var uniqueVertices = mutableSetOf<VT>()
|
private var uniqueVertices = mutableSetOf<VT>()
|
||||||
|
176
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
176
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
|
||||||
|
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(functionCallStatement.target.nameInSource == listOf("swap")) {
|
||||||
|
// if x and y are both just identifiers, do not rewrite (there should be asm generation for that)
|
||||||
|
// otherwise:
|
||||||
|
// rewrite swap(x,y) as follows:
|
||||||
|
// - declare a temp variable of the same datatype
|
||||||
|
// - temp = x, x = y, y= temp
|
||||||
|
val first = functionCallStatement.args[0]
|
||||||
|
val second = functionCallStatement.args[1]
|
||||||
|
if(first !is IdentifierReference && second !is IdentifierReference) {
|
||||||
|
val dt = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}"
|
||||||
|
val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position)
|
||||||
|
val tempvar = IdentifierReference(listOf(tempname), first.position)
|
||||||
|
val assignTemp = Assignment(
|
||||||
|
AssignTarget(tempvar, null, null, first.position),
|
||||||
|
null,
|
||||||
|
first,
|
||||||
|
first.position
|
||||||
|
)
|
||||||
|
val assignFirst = Assignment(
|
||||||
|
AssignTarget.fromExpr(first),
|
||||||
|
null,
|
||||||
|
second,
|
||||||
|
first.position
|
||||||
|
)
|
||||||
|
val assignSecond = Assignment(
|
||||||
|
AssignTarget.fromExpr(second),
|
||||||
|
null,
|
||||||
|
tempvar,
|
||||||
|
first.position
|
||||||
|
)
|
||||||
|
val scope = AnonymousScope(mutableListOf(tempvardecl, assignTemp, assignFirst, assignSecond), first.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(functionCallStatement, scope, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
|
||||||
|
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
|
||||||
|
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
functionCall, typecast, parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
// is it a struct variable? then define all its struct members as mangled names,
|
||||||
|
// and include the original decl as well.
|
||||||
|
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
|
||||||
|
val decls = decl.flattenStructMembers()
|
||||||
|
decls.add(decl)
|
||||||
|
val result = AnonymousScope(decls, decl.position)
|
||||||
|
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:
|
||||||
|
// inject subroutine params as local variables (if they're not there yet).
|
||||||
|
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||||
|
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||||
|
if(subroutine.asmAddress==null) {
|
||||||
|
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||||
|
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||||
|
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||||
|
return subroutine.parameters
|
||||||
|
.filter { it.name !in namesInSub }
|
||||||
|
.map {
|
||||||
|
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||||
|
IAstModification.InsertFirst(vardecl, subroutine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
when {
|
||||||
|
expr.left is StringLiteralValue ->
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
expr.right is StringLiteralValue ->
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(string.parent !is VarDecl) {
|
||||||
|
// replace the literal string by a identifier reference to a new local vardecl
|
||||||
|
val vardecl = VarDecl.createAuto(string)
|
||||||
|
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(string, identifier, parent),
|
||||||
|
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||||
|
val vardecl = array.parent as? VarDecl
|
||||||
|
if(vardecl!=null) {
|
||||||
|
// adjust the datatype of the array (to an educated guess)
|
||||||
|
val arrayDt = array.type
|
||||||
|
if(!arrayDt.istype(vardecl.datatype)) {
|
||||||
|
val cast = array.cast(vardecl.datatype)
|
||||||
|
if (cast != null && cast!=array)
|
||||||
|
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val arrayDt = array.guessDatatype(program)
|
||||||
|
if(arrayDt.isKnown) {
|
||||||
|
// this array literal is part of an expression, turn it into an identifier reference
|
||||||
|
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||||
|
if(litval2!=null && litval2!=array) {
|
||||||
|
val vardecl2 = VarDecl.createAuto(litval2)
|
||||||
|
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(array, identifier, parent),
|
||||||
|
IAstModification.InsertFirst(vardecl2, array.definingScope() as Node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
||||||
|
val constvalue = operand.constValue(program)
|
||||||
|
if(constvalue!=null) {
|
||||||
|
if (expr.operator == "*") {
|
||||||
|
// repeat a string a number of times
|
||||||
|
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(expr.operator == "+" && operand is StringLiteralValue) {
|
||||||
|
// concatenate two strings
|
||||||
|
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
|
||||||
|
}
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
}
|
437
compiler/src/prog8/ast/processing/AstWalker.kt
Normal file
437
compiler/src/prog8/ast/processing/AstWalker.kt
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.*
|
||||||
|
import prog8.ast.base.FatalAstException
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
|
||||||
|
interface IAstModification {
|
||||||
|
fun perform()
|
||||||
|
|
||||||
|
class Remove(val node: Node, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if(parent is INameScope) {
|
||||||
|
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
|
||||||
|
throw FatalAstException("attempt to remove non-existing node $node")
|
||||||
|
} else {
|
||||||
|
throw FatalAstException("parent of a remove modification is not an INameScope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetExpression(val setter: (newExpr: Expression) -> Unit, val newExpr: Expression, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
setter(newExpr)
|
||||||
|
newExpr.linkParents(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertFirst(val stmt: Statement, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if(parent is INameScope) {
|
||||||
|
parent.statements.add(0, stmt)
|
||||||
|
stmt.linkParents(parent)
|
||||||
|
} else {
|
||||||
|
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if(parent is INameScope) {
|
||||||
|
parent.statements.add(stmt)
|
||||||
|
stmt.linkParents(parent)
|
||||||
|
} else {
|
||||||
|
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if(parent is INameScope) {
|
||||||
|
val idx = parent.statements.indexOfFirst { it===after } + 1
|
||||||
|
parent.statements.add(idx, stmt)
|
||||||
|
stmt.linkParents(parent)
|
||||||
|
} else {
|
||||||
|
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
parent.replaceChildNode(node, replacement)
|
||||||
|
replacement.linkParents(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SwapOperands(val expr: BinaryExpression): IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
val tmp = expr.left
|
||||||
|
expr.left = expr.right
|
||||||
|
expr.right = tmp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
|
||||||
|
open fun before(contStmt: Continue, 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()
|
||||||
|
|
||||||
|
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(contStmt: Continue, 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()
|
||||||
|
|
||||||
|
private 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun applyModifications(): Int {
|
||||||
|
modifications.forEach {
|
||||||
|
it.first.perform()
|
||||||
|
}
|
||||||
|
val amount = modifications.size
|
||||||
|
modifications.clear()
|
||||||
|
return amount
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(program: Program) {
|
||||||
|
track(before(program, program), program, program)
|
||||||
|
program.modules.forEach { it.accept(this, program) }
|
||||||
|
track(after(program, program), program, program)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(module: Module, parent: Node) {
|
||||||
|
track(before(module, parent), module, parent)
|
||||||
|
module.statements.forEach{ it.accept(this, module) }
|
||||||
|
track(after(module, parent), module, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(expr: PrefixExpression, parent: Node) {
|
||||||
|
track(before(expr, parent), expr, parent)
|
||||||
|
expr.expression.accept(this, expr)
|
||||||
|
track(after(expr, parent), expr, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(expr: BinaryExpression, parent: Node) {
|
||||||
|
track(before(expr, parent), expr, parent)
|
||||||
|
expr.left.accept(this, expr)
|
||||||
|
expr.right.accept(this, expr)
|
||||||
|
track(after(expr, parent), expr, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(directive: Directive, parent: Node) {
|
||||||
|
track(before(directive, parent), directive, parent)
|
||||||
|
track(after(directive, parent), directive, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(block: Block, parent: Node) {
|
||||||
|
track(before(block, parent), block, parent)
|
||||||
|
block.statements.forEach { it.accept(this, block) }
|
||||||
|
track(after(block, parent), block, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(decl: VarDecl, parent: Node) {
|
||||||
|
track(before(decl, parent), decl, parent)
|
||||||
|
decl.value?.accept(this, decl)
|
||||||
|
decl.arraysize?.accept(this, decl)
|
||||||
|
track(after(decl, parent), decl, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(subroutine: Subroutine, parent: Node) {
|
||||||
|
track(before(subroutine, parent), subroutine, parent)
|
||||||
|
subroutine.statements.forEach { it.accept(this, subroutine) }
|
||||||
|
track(after(subroutine, parent), subroutine, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(functionCall: FunctionCall, parent: Node) {
|
||||||
|
track(before(functionCall, parent), functionCall, parent)
|
||||||
|
functionCall.target.accept(this, functionCall)
|
||||||
|
functionCall.args.forEach { it.accept(this, functionCall) }
|
||||||
|
track(after(functionCall, parent), functionCall, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(functionCallStatement: FunctionCallStatement, parent: Node) {
|
||||||
|
track(before(functionCallStatement, parent), functionCallStatement, parent)
|
||||||
|
functionCallStatement.target.accept(this, functionCallStatement)
|
||||||
|
functionCallStatement.args.forEach { it.accept(this, functionCallStatement) }
|
||||||
|
track(after(functionCallStatement, parent), functionCallStatement, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(identifier: IdentifierReference, parent: Node) {
|
||||||
|
track(before(identifier, parent), identifier, parent)
|
||||||
|
track(after(identifier, parent), identifier, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(jump: Jump, parent: Node) {
|
||||||
|
track(before(jump, parent), jump, parent)
|
||||||
|
jump.identifier?.accept(this, jump)
|
||||||
|
track(after(jump, parent), jump, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(ifStatement: IfStatement, parent: Node) {
|
||||||
|
track(before(ifStatement, parent), ifStatement, parent)
|
||||||
|
ifStatement.condition.accept(this, ifStatement)
|
||||||
|
ifStatement.truepart.accept(this, ifStatement)
|
||||||
|
ifStatement.elsepart.accept(this, ifStatement)
|
||||||
|
track(after(ifStatement, parent), ifStatement, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(branchStatement: BranchStatement, parent: Node) {
|
||||||
|
track(before(branchStatement, parent), branchStatement, parent)
|
||||||
|
branchStatement.truepart.accept(this, branchStatement)
|
||||||
|
branchStatement.elsepart.accept(this, branchStatement)
|
||||||
|
track(after(branchStatement, parent), branchStatement, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(range: RangeExpr, parent: Node) {
|
||||||
|
track(before(range, parent), range, parent)
|
||||||
|
range.from.accept(this, range)
|
||||||
|
range.to.accept(this, range)
|
||||||
|
range.step.accept(this, range)
|
||||||
|
track(after(range, parent), range, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(label: Label, parent: Node) {
|
||||||
|
track(before(label, parent), label, parent)
|
||||||
|
track(after(label, parent), label, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(numLiteral: NumericLiteralValue, parent: Node) {
|
||||||
|
track(before(numLiteral, parent), numLiteral, parent)
|
||||||
|
track(after(numLiteral, parent), numLiteral, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(string: StringLiteralValue, parent: Node) {
|
||||||
|
track(before(string, parent), string, parent)
|
||||||
|
track(after(string, parent), string, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(array: ArrayLiteralValue, parent: Node) {
|
||||||
|
track(before(array, parent), array, parent)
|
||||||
|
array.value.forEach { v->v.accept(this, array) }
|
||||||
|
track(after(array, parent), array, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(assignment: Assignment, parent: Node) {
|
||||||
|
track(before(assignment, parent), assignment, parent)
|
||||||
|
assignment.target.accept(this, assignment)
|
||||||
|
assignment.value.accept(this, assignment)
|
||||||
|
track(after(assignment, parent), assignment, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(postIncrDecr: PostIncrDecr, parent: Node) {
|
||||||
|
track(before(postIncrDecr, parent), postIncrDecr, parent)
|
||||||
|
postIncrDecr.target.accept(this, postIncrDecr)
|
||||||
|
track(after(postIncrDecr, parent), postIncrDecr, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(contStmt: Continue, parent: Node) {
|
||||||
|
track(before(contStmt, parent), contStmt, parent)
|
||||||
|
track(after(contStmt, parent), contStmt, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(breakStmt: Break, parent: Node) {
|
||||||
|
track(before(breakStmt, parent), breakStmt, parent)
|
||||||
|
track(after(breakStmt, parent), breakStmt, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(forLoop: ForLoop, parent: Node) {
|
||||||
|
track(before(forLoop, parent), forLoop, parent)
|
||||||
|
forLoop.loopVar.accept(this, forLoop)
|
||||||
|
forLoop.iterable.accept(this, forLoop)
|
||||||
|
forLoop.body.accept(this, forLoop)
|
||||||
|
track(after(forLoop, parent), forLoop, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(whileLoop: WhileLoop, parent: Node) {
|
||||||
|
track(before(whileLoop, parent), whileLoop, parent)
|
||||||
|
whileLoop.condition.accept(this, whileLoop)
|
||||||
|
whileLoop.body.accept(this, whileLoop)
|
||||||
|
track(after(whileLoop, parent), whileLoop, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(repeatLoop: RepeatLoop, parent: Node) {
|
||||||
|
track(before(repeatLoop, parent), repeatLoop, parent)
|
||||||
|
repeatLoop.iterations?.accept(this, repeatLoop)
|
||||||
|
repeatLoop.body.accept(this, repeatLoop)
|
||||||
|
track(after(repeatLoop, parent), repeatLoop, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(untilLoop: UntilLoop, parent: Node) {
|
||||||
|
track(before(untilLoop, parent), untilLoop, parent)
|
||||||
|
untilLoop.untilCondition.accept(this, untilLoop)
|
||||||
|
untilLoop.body.accept(this, untilLoop)
|
||||||
|
track(after(untilLoop, parent), untilLoop, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(returnStmt: Return, parent: Node) {
|
||||||
|
track(before(returnStmt, parent), returnStmt, parent)
|
||||||
|
returnStmt.value?.accept(this, returnStmt)
|
||||||
|
track(after(returnStmt, parent), returnStmt, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
|
||||||
|
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||||
|
arrayIndexedExpression.identifier.accept(this, arrayIndexedExpression)
|
||||||
|
arrayIndexedExpression.arrayspec.accept(this, arrayIndexedExpression)
|
||||||
|
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(assignTarget: AssignTarget, parent: Node) {
|
||||||
|
track(before(assignTarget, parent), assignTarget, parent)
|
||||||
|
assignTarget.arrayindexed?.accept(this, assignTarget)
|
||||||
|
assignTarget.identifier?.accept(this, assignTarget)
|
||||||
|
assignTarget.memoryAddress?.accept(this, assignTarget)
|
||||||
|
track(after(assignTarget, parent), assignTarget, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(scope: AnonymousScope, parent: Node) {
|
||||||
|
track(before(scope, parent), scope, parent)
|
||||||
|
scope.statements.forEach { it.accept(this, scope) }
|
||||||
|
track(after(scope, parent), scope, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(typecast: TypecastExpression, parent: Node) {
|
||||||
|
track(before(typecast, parent), typecast, parent)
|
||||||
|
typecast.expression.accept(this, typecast)
|
||||||
|
track(after(typecast, parent), typecast, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(memread: DirectMemoryRead, parent: Node) {
|
||||||
|
track(before(memread, parent), memread, parent)
|
||||||
|
memread.addressExpression.accept(this, memread)
|
||||||
|
track(after(memread, parent), memread, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(memwrite: DirectMemoryWrite, parent: Node) {
|
||||||
|
track(before(memwrite, parent), memwrite, parent)
|
||||||
|
memwrite.addressExpression.accept(this, memwrite)
|
||||||
|
track(after(memwrite, parent), memwrite, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(addressOf: AddressOf, parent: Node) {
|
||||||
|
track(before(addressOf, parent), addressOf, parent)
|
||||||
|
addressOf.identifier.accept(this, addressOf)
|
||||||
|
track(after(addressOf, parent), addressOf, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(inlineAssembly: InlineAssembly, parent: Node) {
|
||||||
|
track(before(inlineAssembly, parent), inlineAssembly, parent)
|
||||||
|
track(after(inlineAssembly, parent), inlineAssembly, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) {
|
||||||
|
track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
|
||||||
|
track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(nopStatement: NopStatement, parent: Node) {
|
||||||
|
track(before(nopStatement, parent), nopStatement, parent)
|
||||||
|
track(after(nopStatement, parent), nopStatement, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(whenStatement: WhenStatement, parent: Node) {
|
||||||
|
track(before(whenStatement, parent), whenStatement, parent)
|
||||||
|
whenStatement.condition.accept(this, whenStatement)
|
||||||
|
whenStatement.choices.forEach { it.accept(this, whenStatement) }
|
||||||
|
track(after(whenStatement, parent), whenStatement, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(whenChoice: WhenChoice, parent: Node) {
|
||||||
|
track(before(whenChoice, parent), whenChoice, parent)
|
||||||
|
whenChoice.values?.forEach { it.accept(this, whenChoice) }
|
||||||
|
whenChoice.statements.accept(this, whenChoice)
|
||||||
|
track(after(whenChoice, parent), whenChoice, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun visit(structDecl: StructDecl, parent: Node) {
|
||||||
|
track(before(structDecl, parent), structDecl, parent)
|
||||||
|
structDecl.statements.forEach { it.accept(this, structDecl) }
|
||||||
|
track(after(structDecl, parent), structDecl, parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,261 +0,0 @@
|
|||||||
package prog8.ast.processing
|
|
||||||
|
|
||||||
import prog8.ast.Module
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.FatalAstException
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.statements.*
|
|
||||||
|
|
||||||
interface IAstModifyingVisitor {
|
|
||||||
fun visit(program: Program) {
|
|
||||||
program.modules.forEach { visit(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(module: Module) {
|
|
||||||
module.statements = module.statements.map { it.accept(this) }.toMutableList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(expr: PrefixExpression): Expression {
|
|
||||||
expr.expression = expr.expression.accept(this)
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(expr: BinaryExpression): Expression {
|
|
||||||
expr.left = expr.left.accept(this)
|
|
||||||
expr.right = expr.right.accept(this)
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(directive: Directive): Statement {
|
|
||||||
return directive
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(block: Block): Statement {
|
|
||||||
block.statements = block.statements.map { it.accept(this) }.toMutableList()
|
|
||||||
return block
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(decl: VarDecl): Statement {
|
|
||||||
decl.value = decl.value?.accept(this)
|
|
||||||
decl.arraysize?.accept(this)
|
|
||||||
return decl
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(subroutine: Subroutine): Statement {
|
|
||||||
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
|
|
||||||
return subroutine
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(functionCall: FunctionCall): Expression {
|
|
||||||
val newtarget = functionCall.target.accept(this)
|
|
||||||
if(newtarget is IdentifierReference)
|
|
||||||
functionCall.target = newtarget
|
|
||||||
else
|
|
||||||
throw FatalAstException("cannot change class of function call target")
|
|
||||||
functionCall.args = functionCall.args.map { it.accept(this) }.toMutableList()
|
|
||||||
return functionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
|
||||||
val newtarget = functionCallStatement.target.accept(this)
|
|
||||||
if(newtarget is IdentifierReference)
|
|
||||||
functionCallStatement.target = newtarget
|
|
||||||
else
|
|
||||||
throw FatalAstException("cannot change class of function call target")
|
|
||||||
functionCallStatement.args = functionCallStatement.args.map { it.accept(this) }.toMutableList()
|
|
||||||
return functionCallStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(identifier: IdentifierReference): Expression {
|
|
||||||
// note: this is an identifier that is used in an expression.
|
|
||||||
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
|
|
||||||
return identifier
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(jump: Jump): Statement {
|
|
||||||
if(jump.identifier!=null) {
|
|
||||||
val ident = jump.identifier.accept(this)
|
|
||||||
if(ident is IdentifierReference && ident!==jump.identifier) {
|
|
||||||
return Jump(null, ident, null, jump.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return jump
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(ifStatement: IfStatement): Statement {
|
|
||||||
ifStatement.condition = ifStatement.condition.accept(this)
|
|
||||||
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
|
|
||||||
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
|
|
||||||
return ifStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(branchStatement: BranchStatement): Statement {
|
|
||||||
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
|
|
||||||
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
|
|
||||||
return branchStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(range: RangeExpr): Expression {
|
|
||||||
range.from = range.from.accept(this)
|
|
||||||
range.to = range.to.accept(this)
|
|
||||||
range.step = range.step.accept(this)
|
|
||||||
return range
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(label: Label): Statement {
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
|
|
||||||
return literalValue
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(stringLiteral: StringLiteralValue): Expression {
|
|
||||||
return stringLiteral
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
|
||||||
for(av in arrayLiteral.value.withIndex()) {
|
|
||||||
val newvalue = av.value.accept(this)
|
|
||||||
arrayLiteral.value[av.index] = newvalue
|
|
||||||
}
|
|
||||||
return arrayLiteral
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(assignment: Assignment): Statement {
|
|
||||||
assignment.target = assignment.target.accept(this)
|
|
||||||
assignment.value = assignment.value.accept(this)
|
|
||||||
return assignment
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(postIncrDecr: PostIncrDecr): Statement {
|
|
||||||
postIncrDecr.target = postIncrDecr.target.accept(this)
|
|
||||||
return postIncrDecr
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(contStmt: Continue): Statement {
|
|
||||||
return contStmt
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(breakStmt: Break): Statement {
|
|
||||||
return breakStmt
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(forLoop: ForLoop): Statement {
|
|
||||||
when(val newloopvar = forLoop.loopVar?.accept(this)) {
|
|
||||||
is IdentifierReference -> forLoop.loopVar = newloopvar
|
|
||||||
null -> forLoop.loopVar = null
|
|
||||||
else -> throw FatalAstException("can't change class of loopvar")
|
|
||||||
}
|
|
||||||
forLoop.iterable = forLoop.iterable.accept(this)
|
|
||||||
forLoop.body = forLoop.body.accept(this) as AnonymousScope
|
|
||||||
return forLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(whileLoop: WhileLoop): Statement {
|
|
||||||
whileLoop.condition = whileLoop.condition.accept(this)
|
|
||||||
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
|
|
||||||
return whileLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(repeatLoop: RepeatLoop): Statement {
|
|
||||||
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
|
|
||||||
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
|
|
||||||
return repeatLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(returnStmt: Return): Statement {
|
|
||||||
returnStmt.value = returnStmt.value?.accept(this)
|
|
||||||
return returnStmt
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
|
|
||||||
val ident = arrayIndexedExpression.identifier.accept(this)
|
|
||||||
if(ident is IdentifierReference)
|
|
||||||
arrayIndexedExpression.identifier = ident
|
|
||||||
arrayIndexedExpression.arrayspec.accept(this)
|
|
||||||
return arrayIndexedExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(assignTarget: AssignTarget): AssignTarget {
|
|
||||||
when (val ident = assignTarget.identifier?.accept(this)) {
|
|
||||||
is IdentifierReference -> assignTarget.identifier = ident
|
|
||||||
null -> assignTarget.identifier = null
|
|
||||||
else -> throw FatalAstException("can't change class of assign target identifier")
|
|
||||||
}
|
|
||||||
assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this)
|
|
||||||
assignTarget.memoryAddress?.let { visit(it) }
|
|
||||||
return assignTarget
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(scope: AnonymousScope): Statement {
|
|
||||||
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
|
|
||||||
return scope
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(typecast: TypecastExpression): Expression {
|
|
||||||
typecast.expression = typecast.expression.accept(this)
|
|
||||||
return typecast
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(memread: DirectMemoryRead): Expression {
|
|
||||||
memread.addressExpression = memread.addressExpression.accept(this)
|
|
||||||
return memread
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(memwrite: DirectMemoryWrite) {
|
|
||||||
memwrite.addressExpression = memwrite.addressExpression.accept(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(addressOf: AddressOf): Expression {
|
|
||||||
val ident = addressOf.identifier.accept(this)
|
|
||||||
if(ident is IdentifierReference)
|
|
||||||
addressOf.identifier = ident
|
|
||||||
else
|
|
||||||
throw FatalAstException("can't change class of addressof identifier")
|
|
||||||
return addressOf
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(inlineAssembly: InlineAssembly): Statement {
|
|
||||||
return inlineAssembly
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(registerExpr: RegisterExpr): Expression {
|
|
||||||
return registerExpr
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
|
|
||||||
return builtinFunctionStatementPlaceholder
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(nopStatement: NopStatement): Statement {
|
|
||||||
return nopStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(whenStatement: WhenStatement): Statement {
|
|
||||||
whenStatement.condition = whenStatement.condition.accept(this)
|
|
||||||
whenStatement.choices.forEach { it.accept(this) }
|
|
||||||
return whenStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(whenChoice: WhenChoice) {
|
|
||||||
whenChoice.values = whenChoice.values?.map { it.accept(this) }
|
|
||||||
val stmt = whenChoice.statements.accept(this)
|
|
||||||
if(stmt is AnonymousScope)
|
|
||||||
whenChoice.statements = stmt
|
|
||||||
else {
|
|
||||||
whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position)
|
|
||||||
whenChoice.statements.linkParents(whenChoice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(structDecl: StructDecl): Statement {
|
|
||||||
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
|
|
||||||
return structDecl
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(structLv: StructLiteralValue): Expression {
|
|
||||||
structLv.values = structLv.values.map { it.accept(this) }
|
|
||||||
return structLv
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ import prog8.ast.statements.*
|
|||||||
|
|
||||||
interface IAstVisitor {
|
interface IAstVisitor {
|
||||||
fun visit(program: Program) {
|
fun visit(program: Program) {
|
||||||
program.modules.forEach { visit(it) }
|
program.modules.forEach { it.accept(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(module: Module) {
|
fun visit(module: Module) {
|
||||||
@ -102,7 +102,7 @@ interface IAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun visit(forLoop: ForLoop) {
|
fun visit(forLoop: ForLoop) {
|
||||||
forLoop.loopVar?.accept(this)
|
forLoop.loopVar.accept(this)
|
||||||
forLoop.iterable.accept(this)
|
forLoop.iterable.accept(this)
|
||||||
forLoop.body.accept(this)
|
forLoop.body.accept(this)
|
||||||
}
|
}
|
||||||
@ -113,10 +113,15 @@ interface IAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun visit(repeatLoop: RepeatLoop) {
|
fun visit(repeatLoop: RepeatLoop) {
|
||||||
repeatLoop.untilCondition.accept(this)
|
repeatLoop.iterations?.accept(this)
|
||||||
repeatLoop.body.accept(this)
|
repeatLoop.body.accept(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun visit(untilLoop: UntilLoop) {
|
||||||
|
untilLoop.untilCondition.accept(this)
|
||||||
|
untilLoop.body.accept(this)
|
||||||
|
}
|
||||||
|
|
||||||
fun visit(returnStmt: Return) {
|
fun visit(returnStmt: Return) {
|
||||||
returnStmt.value?.accept(this)
|
returnStmt.value?.accept(this)
|
||||||
}
|
}
|
||||||
@ -155,9 +160,6 @@ interface IAstVisitor {
|
|||||||
fun visit(inlineAssembly: InlineAssembly) {
|
fun visit(inlineAssembly: InlineAssembly) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(registerExpr: RegisterExpr) {
|
|
||||||
}
|
|
||||||
|
|
||||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,8 +179,4 @@ interface IAstVisitor {
|
|||||||
fun visit(structDecl: StructDecl) {
|
fun visit(structDecl: StructDecl) {
|
||||||
structDecl.statements.forEach { it.accept(this) }
|
structDecl.statements.forEach { it.accept(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun visit(structLv: StructLiteralValue) {
|
|
||||||
structLv.values.forEach { it.accept(this) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,21 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.Module
|
import prog8.ast.Node
|
||||||
import prog8.ast.base.SyntaxError
|
|
||||||
import prog8.ast.base.printWarning
|
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
import prog8.ast.statements.Statement
|
|
||||||
|
|
||||||
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
|
|
||||||
private val checkResult: MutableList<SyntaxError> = mutableListOf()
|
|
||||||
|
|
||||||
internal fun result(): List<SyntaxError> {
|
|
||||||
return checkResult
|
|
||||||
}
|
|
||||||
|
|
||||||
|
internal class ImportedModuleDirectiveRemover: AstWalker() {
|
||||||
/**
|
/**
|
||||||
* Most global directives don't apply for imported modules, so remove them
|
* Most global directives don't apply for imported modules, so remove them
|
||||||
*/
|
*/
|
||||||
override fun visit(module: Module) {
|
|
||||||
super.visit(module)
|
|
||||||
val newStatements : MutableList<Statement> = mutableListOf()
|
|
||||||
|
|
||||||
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||||
for (sourceStmt in module.statements) {
|
private val noModifications = emptyList<IAstModification>()
|
||||||
val stmt = sourceStmt.accept(this)
|
|
||||||
if(stmt is Directive && stmt.parent is Module) {
|
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
|
||||||
if(stmt.directive in moduleLevelDirectives) {
|
if(directive.directive in moduleLevelDirectives) {
|
||||||
printWarning("ignoring module directive because it was imported", stmt.position, stmt.directive)
|
return listOf(IAstModification.Remove(directive, parent))
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
return noModifications
|
||||||
newStatements.add(stmt)
|
|
||||||
}
|
|
||||||
module.statements = newStatements
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
71
compiler/src/prog8/ast/processing/ReflectionAstWalker.kt
Normal file
71
compiler/src/prog8/ast/processing/ReflectionAstWalker.kt
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is here for reference only, reflection based ast walking is very slow
|
||||||
|
when compared to the more verbose visitor pattern interfaces.
|
||||||
|
Too bad, because the code is very small
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
//import prog8.ast.NoAstWalk
|
||||||
|
//import prog8.ast.Node
|
||||||
|
//import prog8.ast.Program
|
||||||
|
//import prog8.ast.base.Position
|
||||||
|
//import prog8.ast.expressions.BinaryExpression
|
||||||
|
//import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
//import kotlin.reflect.KClass
|
||||||
|
//import kotlin.reflect.KVisibility
|
||||||
|
//import kotlin.reflect.full.declaredMemberProperties
|
||||||
|
//import kotlin.reflect.full.isSubtypeOf
|
||||||
|
//import kotlin.reflect.full.starProjectedType
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//class ReflectionAstWalker {
|
||||||
|
// private val nodeType = Node::class.starProjectedType
|
||||||
|
// private val collectionType = Collection::class.starProjectedType
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// fun walk(node: Node, nesting: Int) {
|
||||||
|
// val nodetype: KClass<out Node> = node::class
|
||||||
|
// val indent = " ".repeat(nesting)
|
||||||
|
// //println("$indent VISITING ${nodetype.simpleName}")
|
||||||
|
// val visibleAstMembers = nodetype.declaredMemberProperties.filter {
|
||||||
|
// it.visibility!=KVisibility.PRIVATE && !it.isLateinit &&
|
||||||
|
// !(it.annotations.any{a->a is NoAstWalk})
|
||||||
|
// }
|
||||||
|
// for(prop in visibleAstMembers) {
|
||||||
|
// if(prop.returnType.isSubtypeOf(nodeType)) {
|
||||||
|
// // println("$indent +PROP: ${prop.name}")
|
||||||
|
// walk(prop.call(node) as Node, nesting + 1)
|
||||||
|
// }
|
||||||
|
// else if(prop.returnType.isSubtypeOf(collectionType)) {
|
||||||
|
// val elementType = prop.returnType.arguments.single().type
|
||||||
|
// if(elementType!=null && elementType.isSubtypeOf(nodeType)) {
|
||||||
|
// val nodes = prop.call(node) as Collection<Node>
|
||||||
|
// nodes.forEach { walk(it, nesting+1) }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// fun walk(program: Program) {
|
||||||
|
// for(module in program.modules) {
|
||||||
|
// println("---MODULE $module---")
|
||||||
|
// walk(module, 0)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//fun main() {
|
||||||
|
// val ast = BinaryExpression(
|
||||||
|
// NumericLiteralValue.optimalInteger(100, Position.DUMMY),
|
||||||
|
// "+",
|
||||||
|
// NumericLiteralValue.optimalInteger(200, Position.DUMMY),
|
||||||
|
// Position.DUMMY
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// val walker = ReflectionAstWalker()
|
||||||
|
// walker.walk(ast,0)
|
||||||
|
//
|
||||||
|
//}
|
@ -1,14 +1,149 @@
|
|||||||
package prog8.ast.processing
|
package prog8.ast.processing
|
||||||
|
|
||||||
import prog8.ast.*
|
import prog8.ast.*
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.FatalAstException
|
|
||||||
import prog8.ast.base.initvarsSubName
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
|
||||||
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
|
internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||||
|
// Reorders the statements in a way the compiler needs.
|
||||||
|
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||||
|
// - library blocks are put last.
|
||||||
|
// - blocks are ordered by address, where blocks without address are placed last.
|
||||||
|
// - in every scope, most directives and vardecls are moved to the top.
|
||||||
|
// - the 'start' subroutine is moved to the top.
|
||||||
|
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||||
|
// - (syntax desugaring) augmented assignment is turned into regular assignment.
|
||||||
|
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
|
||||||
|
// - 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> {
|
||||||
|
val (blocks, other) = module.statements.partition { it is Block }
|
||||||
|
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
||||||
|
|
||||||
|
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||||
|
if(mainBlock!=null && mainBlock.address==null) {
|
||||||
|
module.statements.remove(mainBlock)
|
||||||
|
module.statements.add(0, mainBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
reorderVardeclsAndDirectives(module.statements)
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
|
||||||
|
val varDecls = statements.filterIsInstance<VarDecl>()
|
||||||
|
statements.removeAll(varDecls)
|
||||||
|
statements.addAll(0, varDecls)
|
||||||
|
|
||||||
|
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
|
||||||
|
statements.removeAll(directives)
|
||||||
|
statements.addAll(0, directives)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
||||||
|
parent as Module
|
||||||
|
if(block.isInLibrary) {
|
||||||
|
return listOf(
|
||||||
|
IAstModification.Remove(block, parent),
|
||||||
|
IAstModification.InsertLast(block, parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
reorderVardeclsAndDirectives(block.statements)
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(subroutine.name=="start" && parent is Block) {
|
||||||
|
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
|
||||||
|
return listOf(
|
||||||
|
IAstModification.Remove(subroutine, parent),
|
||||||
|
IAstModification.InsertFirst(subroutine, parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
val declValue = decl.value
|
||||||
|
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||||
|
val declConstValue = declValue.constValue(program)
|
||||||
|
if(declConstValue==null) {
|
||||||
|
// move the vardecl (without value) to the scope and replace this with a regular assignment
|
||||||
|
decl.value = null
|
||||||
|
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||||
|
val assign = Assignment(target, null, declValue, decl.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(decl, assign, parent),
|
||||||
|
IAstModification.InsertFirst(decl, decl.definingScope() as Node)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||||
|
it.first?.first() ?: Int.MAX_VALUE
|
||||||
|
}
|
||||||
|
whenStatement.choices.clear()
|
||||||
|
choices.mapTo(whenStatement.choices) { it.second }
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(assignment.aug_op!=null) {
|
||||||
|
return listOf(IAstModification.ReplaceNode(assignment, assignment.asDesugaredNonaugmented(), parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
val valueType = assignment.value.inferType(program)
|
||||||
|
val targetType = assignment.target.inferType(program, assignment)
|
||||||
|
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
|
||||||
|
val assignments = if (assignment.value is ArrayLiteralValue) {
|
||||||
|
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
|
||||||
|
} else {
|
||||||
|
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
|
||||||
|
}
|
||||||
|
if(assignments.isNotEmpty()) {
|
||||||
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
|
||||||
|
modifications.add(IAstModification.Remove(assignment, parent))
|
||||||
|
return modifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||||
|
val identifier = structAssignment.target.identifier!!
|
||||||
|
val identifierName = identifier.nameInSource.single()
|
||||||
|
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||||
|
val struct = targetVar.struct!!
|
||||||
|
|
||||||
|
val slv = structAssignment.value as? ArrayLiteralValue
|
||||||
|
if(slv==null || slv.value.size != struct.numberOfElements)
|
||||||
|
throw FatalAstException("element count mismatch")
|
||||||
|
|
||||||
|
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),
|
||||||
|
null, sourceValue, sourceValue.position)
|
||||||
|
assign.linkParents(structAssignment)
|
||||||
|
assign
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||||
val identifier = structAssignment.target.identifier!!
|
val identifier = structAssignment.target.identifier!!
|
||||||
val identifierName = identifier.nameInSource.single()
|
val identifierName = identifier.nameInSource.single()
|
||||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||||
@ -33,226 +168,17 @@ private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment,
|
|||||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
|
||||||
null, sourceIdref, member.second.position)
|
null, sourceIdref, member.second.position)
|
||||||
assign.linkParents(structAssignment)
|
assign.linkParents(structAssignment)
|
||||||
assign
|
assign
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is StructLiteralValue -> {
|
is ArrayLiteralValue -> {
|
||||||
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||||
}
|
}
|
||||||
else -> throw FatalAstException("strange struct value")
|
else -> throw FatalAstException("strange struct value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
|
|
||||||
// Reorders the statements in a way the compiler needs.
|
|
||||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
|
||||||
// - blocks are ordered by address, where blocks without address are put at the end.
|
|
||||||
// - in every scope:
|
|
||||||
// -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first.
|
|
||||||
// -- all vardecls then follow.
|
|
||||||
// -- the remaining statements then follow in their original order.
|
|
||||||
//
|
|
||||||
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
|
|
||||||
// - all other subroutines will be moved to the end of their block.
|
|
||||||
// - sorts the choices in when statement.
|
|
||||||
|
|
||||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
|
||||||
|
|
||||||
private val addReturns = mutableListOf<Pair<INameScope, Int>>()
|
|
||||||
|
|
||||||
override fun visit(module: Module) {
|
|
||||||
addReturns.clear()
|
|
||||||
super.visit(module)
|
|
||||||
|
|
||||||
val (blocks, other) = module.statements.partition { it is Block }
|
|
||||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
|
||||||
|
|
||||||
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything
|
|
||||||
val nonLibraryBlocks = module.statements.withIndex()
|
|
||||||
.filter { it.value is Block && !(it.value as Block).isInLibrary }
|
|
||||||
.map { it.index to it.value }
|
|
||||||
.reversed()
|
|
||||||
for(nonLibBlock in nonLibraryBlocks)
|
|
||||||
module.statements.removeAt(nonLibBlock.first)
|
|
||||||
for(nonLibBlock in nonLibraryBlocks)
|
|
||||||
module.statements.add(0, nonLibBlock.second)
|
|
||||||
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
|
|
||||||
if(mainBlock!=null && (mainBlock as Block).address==null) {
|
|
||||||
module.remove(mainBlock)
|
|
||||||
module.statements.add(0, mainBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
val varDecls = module.statements.filterIsInstance<VarDecl>()
|
|
||||||
module.statements.removeAll(varDecls)
|
|
||||||
module.statements.addAll(0, varDecls)
|
|
||||||
|
|
||||||
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
|
|
||||||
module.statements.removeAll(directives)
|
|
||||||
module.statements.addAll(0, directives)
|
|
||||||
|
|
||||||
for(pos in addReturns) {
|
|
||||||
println(pos)
|
|
||||||
val returnStmt = Return(null, pos.first.position)
|
|
||||||
returnStmt.linkParents(pos.first as Node)
|
|
||||||
pos.first.statements.add(pos.second, returnStmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(block: Block): Statement {
|
|
||||||
|
|
||||||
val subroutines = block.statements.filterIsInstance<Subroutine>()
|
|
||||||
var numSubroutinesAtEnd = 0
|
|
||||||
// move all subroutines to the end of the block
|
|
||||||
for (subroutine in subroutines) {
|
|
||||||
if(subroutine.name!="start" || block.name!="main") {
|
|
||||||
block.remove(subroutine)
|
|
||||||
block.statements.add(subroutine)
|
|
||||||
}
|
|
||||||
numSubroutinesAtEnd++
|
|
||||||
}
|
|
||||||
// move the "start" subroutine to the top
|
|
||||||
if(block.name=="main") {
|
|
||||||
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
|
|
||||||
block.remove(it)
|
|
||||||
block.statements.add(0, it)
|
|
||||||
numSubroutinesAtEnd--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure there is a 'return' in front of the first subroutine
|
|
||||||
// (if it isn't the first statement in the block itself, and isn't the program's entrypoint)
|
|
||||||
if(numSubroutinesAtEnd>0 && block.statements.size > (numSubroutinesAtEnd+1)) {
|
|
||||||
val firstSub = block.statements[block.statements.size - numSubroutinesAtEnd] as Subroutine
|
|
||||||
if(firstSub.name != "start" && block.name != "main") {
|
|
||||||
val stmtBeforeFirstSub = block.statements[block.statements.size - numSubroutinesAtEnd - 1]
|
|
||||||
if (stmtBeforeFirstSub !is Return
|
|
||||||
&& stmtBeforeFirstSub !is Jump
|
|
||||||
&& stmtBeforeFirstSub !is Subroutine
|
|
||||||
&& stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) {
|
|
||||||
val ret = Return(null, stmtBeforeFirstSub.position)
|
|
||||||
ret.linkParents(block)
|
|
||||||
block.statements.add(block.statements.size - numSubroutinesAtEnd, ret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val varDecls = block.statements.filterIsInstance<VarDecl>()
|
|
||||||
block.statements.removeAll(varDecls)
|
|
||||||
block.statements.addAll(0, varDecls)
|
|
||||||
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
|
|
||||||
block.statements.removeAll(directives)
|
|
||||||
block.statements.addAll(0, directives)
|
|
||||||
block.linkParents(block.parent)
|
|
||||||
|
|
||||||
// create subroutine that initializes the block's variables (if any)
|
|
||||||
val varInits = block.statements.withIndex().filter { it.value is VariableInitializationAssignment }
|
|
||||||
if(varInits.isNotEmpty()) {
|
|
||||||
val statements = varInits.map{it.value}.toMutableList()
|
|
||||||
val varInitSub = Subroutine(initvarsSubName, emptyList(), emptyList(), emptyList(), emptyList(),
|
|
||||||
emptySet(), null, false, statements, block.position)
|
|
||||||
varInitSub.keepAlways = true
|
|
||||||
varInitSub.linkParents(block)
|
|
||||||
block.statements.add(varInitSub)
|
|
||||||
|
|
||||||
// remove the varinits from the block's statements
|
|
||||||
for(index in varInits.map{it.index}.reversed())
|
|
||||||
block.statements.removeAt(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.visit(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine): Statement {
|
|
||||||
super.visit(subroutine)
|
|
||||||
|
|
||||||
val scope = subroutine.definingScope()
|
|
||||||
if(scope is Subroutine) {
|
|
||||||
for(stmt in scope.statements.withIndex()) {
|
|
||||||
if(stmt.index>0 && stmt.value===subroutine) {
|
|
||||||
val precedingStmt = scope.statements[stmt.index-1]
|
|
||||||
if(precedingStmt !is Jump && precedingStmt !is Subroutine) {
|
|
||||||
// insert a return statement before a nested subroutine, to avoid falling trough inside the subroutine
|
|
||||||
addReturns.add(Pair(scope, stmt.index))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
|
|
||||||
subroutine.statements.removeAll(varDecls)
|
|
||||||
subroutine.statements.addAll(0, varDecls)
|
|
||||||
val directives = subroutine.statements.filter {it is Directive && it.directive in directivesToMove}
|
|
||||||
subroutine.statements.removeAll(directives)
|
|
||||||
subroutine.statements.addAll(0, directives)
|
|
||||||
|
|
||||||
if(subroutine.returntypes.isEmpty()) {
|
|
||||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
|
|
||||||
// and if an assembly block doesn't contain a rts/rti
|
|
||||||
if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) {
|
|
||||||
if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) {
|
|
||||||
val returnStmt = Return(null, subroutine.position)
|
|
||||||
returnStmt.linkParents(subroutine)
|
|
||||||
subroutine.statements.add(returnStmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return subroutine
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): Statement {
|
|
||||||
val assg = super.visit(assignment)
|
|
||||||
if(assg !is Assignment)
|
|
||||||
return assg
|
|
||||||
|
|
||||||
// see if a typecast is needed to convert the value's type into the proper target type
|
|
||||||
val valueItype = assg.value.inferType(program)
|
|
||||||
val targetItype = assg.target.inferType(program, assg)
|
|
||||||
|
|
||||||
if(targetItype.isKnown && valueItype.isKnown) {
|
|
||||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
|
||||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
|
||||||
|
|
||||||
// struct assignments will be flattened (if it's not a struct literal)
|
|
||||||
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) {
|
|
||||||
if (assg.value is StructLiteralValue)
|
|
||||||
return assg // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed)
|
|
||||||
|
|
||||||
val assignments = flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
|
|
||||||
return if (assignments.isEmpty()) {
|
|
||||||
// something went wrong (probably incompatible struct types)
|
|
||||||
// we'll get an error later from the AstChecker
|
|
||||||
assg
|
|
||||||
} else {
|
|
||||||
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
|
|
||||||
scope.linkParents(assg.parent)
|
|
||||||
scope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(assg.aug_op!=null) {
|
|
||||||
// transform augmented assg into normal assg so we have one case less to deal with later
|
|
||||||
val newTarget: Expression =
|
|
||||||
when {
|
|
||||||
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
|
|
||||||
assg.target.identifier != null -> assg.target.identifier!!
|
|
||||||
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
|
|
||||||
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
|
|
||||||
else -> throw FatalAstException("strange assg")
|
|
||||||
}
|
|
||||||
|
|
||||||
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
|
|
||||||
expression.linkParents(assg.parent)
|
|
||||||
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
|
|
||||||
convertedAssignment.linkParents(assg.parent)
|
|
||||||
return super.visit(convertedAssignment)
|
|
||||||
}
|
|
||||||
|
|
||||||
return assg
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,80 +2,93 @@ package prog8.ast.processing
|
|||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
import prog8.ast.INameScope
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.FatalAstException
|
|
||||||
import prog8.ast.base.printWarning
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
|
||||||
internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor {
|
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : 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)
|
* 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)
|
||||||
|
*/
|
||||||
|
|
||||||
override fun visit(expr: BinaryExpression): Expression {
|
private val noModifications = emptyList<IAstModification>()
|
||||||
val expr2 = super.visit(expr)
|
|
||||||
if(expr2 !is BinaryExpression)
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
return expr2
|
val leftDt = expr.left.inferType(program)
|
||||||
val leftDt = expr2.left.inferType(program)
|
val rightDt = expr.right.inferType(program)
|
||||||
val rightDt = expr2.right.inferType(program)
|
|
||||||
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
||||||
// determine common datatype and add typecast as required to make left and right equal types
|
// determine common datatype and add typecast as required to make left and right equal types
|
||||||
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr2.left, expr2.right)
|
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr.left, expr.right)
|
||||||
if(toFix!=null) {
|
if(toFix!=null) {
|
||||||
when {
|
return when {
|
||||||
toFix===expr2.left -> {
|
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||||
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
|
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr))
|
||||||
expr2.left.linkParents(expr2)
|
toFix===expr.right -> listOf(IAstModification.ReplaceNode(
|
||||||
}
|
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
|
||||||
toFix===expr2.right -> {
|
|
||||||
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
|
|
||||||
expr2.right.linkParents(expr2)
|
|
||||||
}
|
|
||||||
else -> throw FatalAstException("confused binary expression side")
|
else -> throw FatalAstException("confused binary expression side")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return expr2
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): Statement {
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
val assg = super.visit(assignment)
|
|
||||||
if(assg !is Assignment)
|
|
||||||
return assg
|
|
||||||
|
|
||||||
// see if a typecast is needed to convert the value's type into the proper target type
|
// see if a typecast is needed to convert the value's type into the proper target type
|
||||||
val valueItype = assg.value.inferType(program)
|
val valueItype = assignment.value.inferType(program)
|
||||||
val targetItype = assg.target.inferType(program, assg)
|
val targetItype = assignment.target.inferType(program, assignment)
|
||||||
|
|
||||||
if(targetItype.isKnown && valueItype.isKnown) {
|
if(targetItype.isKnown && valueItype.isKnown) {
|
||||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||||
if (valuetype != targettype) {
|
if (valuetype != targettype) {
|
||||||
if (valuetype isAssignableTo targettype) {
|
if (valuetype isAssignableTo targettype) {
|
||||||
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
|
return listOf(IAstModification.ReplaceNode(
|
||||||
assg.value.linkParents(assg)
|
assignment.value,
|
||||||
}
|
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
assignment))
|
||||||
|
} else {
|
||||||
|
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> =
|
||||||
|
listOf(IAstModification.ReplaceNode(cvalue, cvalue.cast(targettype), cvalue.parent))
|
||||||
|
val cvalue = assignment.value.constValue(program)
|
||||||
|
if(cvalue!=null) {
|
||||||
|
val number = cvalue.number.toDouble()
|
||||||
|
// more complex comparisons if the type is different, but the constant value is compatible
|
||||||
|
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
|
||||||
|
if(number>0)
|
||||||
|
return castLiteral(cvalue)
|
||||||
|
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
|
||||||
|
if(number>0)
|
||||||
|
return castLiteral(cvalue)
|
||||||
|
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
|
||||||
|
if(number<0x80)
|
||||||
|
return castLiteral(cvalue)
|
||||||
|
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
|
||||||
|
if(number<0x8000)
|
||||||
|
return castLiteral(cvalue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return assg
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
|
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope())
|
||||||
return super.visit(functionCallStatement)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): Expression {
|
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
checkFunctionCallArguments(functionCall, functionCall.definingScope())
|
return afterFunctionCallArgs(functionCall, functionCall.definingScope())
|
||||||
return super.visit(functionCall)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
|
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
|
||||||
// see if a typecast is needed to convert the arguments into the required parameter's type
|
// see if a typecast is needed to convert the arguments into the required parameter's type
|
||||||
|
val modifications = mutableListOf<IAstModification>()
|
||||||
|
|
||||||
when(val sub = call.target.targetStatement(scope)) {
|
when(val sub = call.target.targetStatement(scope)) {
|
||||||
is Subroutine -> {
|
is Subroutine -> {
|
||||||
for(arg in sub.parameters.zip(call.args.withIndex())) {
|
for(arg in sub.parameters.zip(call.args.withIndex())) {
|
||||||
@ -85,19 +98,33 @@ internal class TypecastsAdder(private val program: Program): IAstModifyingVisito
|
|||||||
val requiredType = arg.first.type
|
val requiredType = arg.first.type
|
||||||
if (requiredType != argtype) {
|
if (requiredType != argtype) {
|
||||||
if (argtype isAssignableTo requiredType) {
|
if (argtype isAssignableTo requiredType) {
|
||||||
val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position)
|
modifications += IAstModification.ReplaceNode(
|
||||||
typecasted.linkParents(arg.second.value.parent)
|
call.args[arg.second.index],
|
||||||
call.args[arg.second.index] = typecasted
|
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
|
||||||
|
call as Node)
|
||||||
|
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||||
|
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
||||||
|
modifications += IAstModification.ReplaceNode(
|
||||||
|
call.args[arg.second.index],
|
||||||
|
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||||
|
call as Node)
|
||||||
|
} else if(arg.second.value is NumericLiteralValue) {
|
||||||
|
try {
|
||||||
|
val castedValue = (arg.second.value as NumericLiteralValue).cast(requiredType)
|
||||||
|
modifications += IAstModification.ReplaceNode(
|
||||||
|
call.args[arg.second.index],
|
||||||
|
castedValue,
|
||||||
|
call as Node)
|
||||||
|
} catch (x: ExpressionError) {
|
||||||
|
// no cast possible
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is BuiltinFunctionStatementPlaceholder -> {
|
is BuiltinFunctionStatementPlaceholder -> {
|
||||||
val func = BuiltinFunctions.getValue(sub.name)
|
val func = BuiltinFunctions.getValue(sub.name)
|
||||||
if(func.pure) {
|
|
||||||
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
|
|
||||||
for (arg in func.parameters.zip(call.args.withIndex())) {
|
for (arg in func.parameters.zip(call.args.withIndex())) {
|
||||||
val argItype = arg.second.value.inferType(program)
|
val argItype = arg.second.value.inferType(program)
|
||||||
if (argItype.isKnown) {
|
if (argItype.isKnown) {
|
||||||
@ -106,93 +133,71 @@ internal class TypecastsAdder(private val program: Program): IAstModifyingVisito
|
|||||||
continue
|
continue
|
||||||
for (possibleType in arg.first.possibleDatatypes) {
|
for (possibleType in arg.first.possibleDatatypes) {
|
||||||
if (argtype isAssignableTo possibleType) {
|
if (argtype isAssignableTo possibleType) {
|
||||||
val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position)
|
modifications += IAstModification.ReplaceNode(
|
||||||
typecasted.linkParents(arg.second.value.parent)
|
call.args[arg.second.index],
|
||||||
call.args[arg.second.index] = typecasted
|
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
|
||||||
break
|
call as Node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
null -> { }
|
||||||
null -> {}
|
|
||||||
else -> throw FatalAstException("call to something weird $sub ${call.target}")
|
else -> throw FatalAstException("call to something weird $sub ${call.target}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return modifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(typecast: TypecastExpression): Expression {
|
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
// warn about any implicit type casts to Float, because that may not be intended
|
// warn about any implicit type casts to Float, because that may not be intended
|
||||||
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||||
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
|
errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
|
||||||
}
|
}
|
||||||
return super.visit(typecast)
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(memread: DirectMemoryRead): Expression {
|
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||||
// make sure the memory address is an uword
|
// make sure the memory address is an uword
|
||||||
val dt = memread.addressExpression.inferType(program)
|
val dt = memread.addressExpression.inferType(program)
|
||||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||||
val literaladdr = memread.addressExpression as? NumericLiteralValue
|
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
|
||||||
if(literaladdr!=null) {
|
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||||
memread.addressExpression = literaladdr.cast(DataType.UWORD)
|
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||||
} else {
|
|
||||||
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
|
||||||
memread.addressExpression.parent = memread
|
|
||||||
}
|
}
|
||||||
}
|
return noModifications
|
||||||
return super.visit(memread)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(memwrite: DirectMemoryWrite) {
|
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||||
|
// make sure the memory address is an uword
|
||||||
val dt = memwrite.addressExpression.inferType(program)
|
val dt = memwrite.addressExpression.inferType(program)
|
||||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||||
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
|
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
|
||||||
if(literaladdr!=null) {
|
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||||
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
|
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||||
|
// add a typecast to the return type if it doesn't match the subroutine's signature
|
||||||
|
val returnValue = returnStmt.value
|
||||||
|
if(returnValue!=null) {
|
||||||
|
val subroutine = returnStmt.definingSubroutine()!!
|
||||||
|
if(subroutine.returntypes.size==1) {
|
||||||
|
val subReturnType = subroutine.returntypes.first()
|
||||||
|
if (returnValue.inferType(program).istype(subReturnType))
|
||||||
|
return noModifications
|
||||||
|
if (returnValue is NumericLiteralValue) {
|
||||||
|
returnStmt.value = returnValue.cast(subroutine.returntypes.single())
|
||||||
} else {
|
} else {
|
||||||
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
return listOf(IAstModification.ReplaceNode(
|
||||||
memwrite.addressExpression.parent = memwrite
|
returnValue,
|
||||||
}
|
TypecastExpression(returnValue, subReturnType, true, returnValue.position),
|
||||||
}
|
returnStmt))
|
||||||
super.visit(memwrite)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(structLv: StructLiteralValue): Expression {
|
|
||||||
val litval = super.visit(structLv)
|
|
||||||
if(litval !is StructLiteralValue)
|
|
||||||
return litval
|
|
||||||
|
|
||||||
val decl = litval.parent as? VarDecl
|
|
||||||
if(decl != null) {
|
|
||||||
val struct = decl.struct
|
|
||||||
if(struct != null) {
|
|
||||||
addTypecastsIfNeeded(litval, struct)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val assign = litval.parent as? Assignment
|
|
||||||
if (assign != null) {
|
|
||||||
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
|
|
||||||
if(decl2 != null) {
|
|
||||||
val struct = decl2.struct
|
|
||||||
if(struct != null) {
|
|
||||||
addTypecastsIfNeeded(litval, struct)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
return noModifications
|
||||||
|
|
||||||
return litval
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
|
|
||||||
structLv.values = struct.statements.zip(structLv.values).map {
|
|
||||||
val memberDt = (it.first as VarDecl).datatype
|
|
||||||
val valueDt = it.second.inferType(program)
|
|
||||||
if (valueDt.typeOrElse(memberDt) != memberDt)
|
|
||||||
TypecastExpression(it.second, memberDt, true, it.second.position)
|
|
||||||
else
|
|
||||||
it.second
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,156 +0,0 @@
|
|||||||
package prog8.ast.processing
|
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
|
||||||
import prog8.ast.Module
|
|
||||||
import prog8.ast.Node
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.statements.*
|
|
||||||
import prog8.compiler.CompilerException
|
|
||||||
import prog8.functions.BuiltinFunctions
|
|
||||||
import prog8.functions.FunctionSignature
|
|
||||||
|
|
||||||
|
|
||||||
internal class VarInitValueAndAddressOfCreator(private val program: Program): IAstModifyingVisitor {
|
|
||||||
// For VarDecls that declare an initialization value:
|
|
||||||
// Replace the vardecl with an assignment (to set the initial value),
|
|
||||||
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
|
|
||||||
// This makes sure the variables get reset to the intended value on a next run of the program.
|
|
||||||
// Variable decls without a value don't get this treatment, which means they retain the last
|
|
||||||
// value they had when restarting the program.
|
|
||||||
// This is done in a separate step because it interferes with the namespace lookup of symbols
|
|
||||||
// in other ast processors.
|
|
||||||
|
|
||||||
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
|
||||||
|
|
||||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
|
||||||
|
|
||||||
override fun visit(module: Module) {
|
|
||||||
vardeclsToAdd.clear()
|
|
||||||
super.visit(module)
|
|
||||||
// add any new vardecls to the various scopes
|
|
||||||
for((where, decls) in vardeclsToAdd) {
|
|
||||||
where.statements.addAll(0, decls)
|
|
||||||
decls.forEach { it.linkParents(where as Node) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): Statement {
|
|
||||||
super.visit(decl)
|
|
||||||
|
|
||||||
if(decl.isArray && decl.value==null) {
|
|
||||||
// array datatype without initialization value, add list of zeros
|
|
||||||
val arraysize = decl.arraysize!!.size()!!
|
|
||||||
val array = ArrayLiteralValue(decl.datatype,
|
|
||||||
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
|
|
||||||
decl.position)
|
|
||||||
decl.value = array
|
|
||||||
}
|
|
||||||
|
|
||||||
if(decl.type!= VarDeclType.VAR || decl.value==null)
|
|
||||||
return decl
|
|
||||||
|
|
||||||
if(decl.datatype in NumericDatatypes) {
|
|
||||||
val scope = decl.definingScope()
|
|
||||||
addVarDecl(scope, decl.asDefaultValueDecl(null))
|
|
||||||
val declvalue = decl.value!!
|
|
||||||
val value =
|
|
||||||
if(declvalue is NumericLiteralValue)
|
|
||||||
declvalue.cast(decl.datatype)
|
|
||||||
else
|
|
||||||
declvalue
|
|
||||||
val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".")
|
|
||||||
return VariableInitializationAssignment(
|
|
||||||
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
|
|
||||||
null,
|
|
||||||
value,
|
|
||||||
decl.position
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return decl
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): Expression {
|
|
||||||
var parentStatement: Node = functionCall
|
|
||||||
while(parentStatement !is Statement)
|
|
||||||
parentStatement = parentStatement.parent
|
|
||||||
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
|
|
||||||
if(targetStatement!=null) {
|
|
||||||
addAddressOfExprIfNeeded(targetStatement, functionCall.args, parentStatement)
|
|
||||||
} else {
|
|
||||||
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
|
|
||||||
if(builtinFunc!=null)
|
|
||||||
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.args, parentStatement)
|
|
||||||
}
|
|
||||||
return functionCall
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
|
||||||
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
|
|
||||||
if(targetStatement!=null) {
|
|
||||||
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.args, functionCallStatement)
|
|
||||||
} else {
|
|
||||||
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
|
|
||||||
if(builtinFunc!=null)
|
|
||||||
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.args, functionCallStatement)
|
|
||||||
}
|
|
||||||
return functionCallStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<Expression>, parent: Statement) {
|
|
||||||
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
|
|
||||||
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
|
|
||||||
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type == DataType.STR) {
|
|
||||||
if(argparam.second is AddressOf)
|
|
||||||
continue
|
|
||||||
val idref = argparam.second as? IdentifierReference
|
|
||||||
val strvalue = argparam.second as? StringLiteralValue
|
|
||||||
if(idref!=null) {
|
|
||||||
val variable = idref.targetVarDecl(program.namespace)
|
|
||||||
if(variable!=null && variable.datatype in IterableDatatypes) {
|
|
||||||
val pointerExpr = AddressOf(idref, idref.position)
|
|
||||||
pointerExpr.linkParents(arglist[argparam.first.index].parent)
|
|
||||||
arglist[argparam.first.index] = pointerExpr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(strvalue!=null) {
|
|
||||||
// add a vardecl so that the autovar can be resolved in later lookups
|
|
||||||
val variable = VarDecl.createAuto(strvalue)
|
|
||||||
addVarDecl(strvalue.definingScope(), variable)
|
|
||||||
// replace the argument with &autovar
|
|
||||||
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
|
|
||||||
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
|
|
||||||
pointerExpr.linkParents(arglist[argparam.first.index].parent)
|
|
||||||
arglist[argparam.first.index] = pointerExpr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) {
|
|
||||||
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
|
||||||
for(arg in args.withIndex().zip(signature.parameters)) {
|
|
||||||
val argvalue = arg.first.value
|
|
||||||
val argDt = argvalue.inferType(program)
|
|
||||||
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
|
|
||||||
if(argvalue !is IdentifierReference)
|
|
||||||
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
|
|
||||||
val addrOf = AddressOf(argvalue, argvalue.position)
|
|
||||||
args[arg.first.index] = addrOf
|
|
||||||
addrOf.linkParents(parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
|
|
||||||
if(scope !in vardeclsToAdd)
|
|
||||||
vardeclsToAdd[scope] = mutableListOf()
|
|
||||||
val declList = vardeclsToAdd.getValue(scope)
|
|
||||||
if(declList.all{it.name!=variable.name})
|
|
||||||
declList.add(variable)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
43
compiler/src/prog8/ast/processing/VariousCleanups.kt
Normal file
43
compiler/src/prog8/ast/processing/VariousCleanups.kt
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
|
import prog8.ast.expressions.TypecastExpression
|
||||||
|
import prog8.ast.statements.AnonymousScope
|
||||||
|
import prog8.ast.statements.NopStatement
|
||||||
|
|
||||||
|
|
||||||
|
internal class VariousCleanups: AstWalker() {
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
return listOf(IAstModification.Remove(nopStatement, parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
|
return if(parent is INameScope)
|
||||||
|
listOf(ScopeFlatten(scope, parent as INameScope))
|
||||||
|
else
|
||||||
|
noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
val idx = into.statements.indexOf(scope)
|
||||||
|
if(idx>=0) {
|
||||||
|
into.statements.addAll(idx+1, scope.statements)
|
||||||
|
into.statements.remove(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(typecast.expression is NumericLiteralValue) {
|
||||||
|
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
|
||||||
|
return listOf(IAstModification.ReplaceNode(typecast, value, parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
}
|
42
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
42
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package prog8.ast.processing
|
||||||
|
|
||||||
|
import prog8.ast.IFunctionCall
|
||||||
|
import prog8.ast.INameScope
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.expressions.FunctionCall
|
||||||
|
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||||
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
|
import prog8.ast.statements.Subroutine
|
||||||
|
import prog8.compiler.CompilerException
|
||||||
|
import prog8.functions.BuiltinFunctions
|
||||||
|
|
||||||
|
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||||
|
|
||||||
|
override fun visit(functionCall: FunctionCall)
|
||||||
|
= checkTypes(functionCall as IFunctionCall, functionCall.definingScope())
|
||||||
|
|
||||||
|
override fun visit(functionCallStatement: FunctionCallStatement)
|
||||||
|
= checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope())
|
||||||
|
|
||||||
|
private fun checkTypes(call: IFunctionCall, scope: INameScope) {
|
||||||
|
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
|
||||||
|
val target = call.target.targetStatement(scope)
|
||||||
|
when(target) {
|
||||||
|
is Subroutine -> {
|
||||||
|
val paramtypes = target.parameters.map { it.type }
|
||||||
|
if(argtypes!=paramtypes)
|
||||||
|
throw CompilerException("parameter type mismatch $call")
|
||||||
|
}
|
||||||
|
is BuiltinFunctionStatementPlaceholder -> {
|
||||||
|
val func = BuiltinFunctions.getValue(target.name)
|
||||||
|
val paramtypes = func.parameters.map { it.possibleDatatypes }
|
||||||
|
for(x in argtypes.zip(paramtypes)) {
|
||||||
|
if(x.first !in x.second)
|
||||||
|
throw CompilerException("parameter type mismatch $call")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,13 +3,14 @@ package prog8.ast.statements
|
|||||||
import prog8.ast.*
|
import prog8.ast.*
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
import prog8.ast.processing.AstWalker
|
||||||
import prog8.ast.processing.IAstVisitor
|
import prog8.ast.processing.IAstVisitor
|
||||||
|
|
||||||
|
|
||||||
sealed class Statement : Node {
|
sealed class Statement : Node {
|
||||||
abstract fun accept(visitor: IAstModifyingVisitor) : Statement
|
|
||||||
abstract fun accept(visitor: IAstVisitor)
|
abstract fun accept(visitor: IAstVisitor)
|
||||||
|
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||||
|
|
||||||
fun makeScopedName(name: String): String {
|
fun makeScopedName(name: String): String {
|
||||||
// easy way out is to always return the full scoped name.
|
// easy way out is to always return the full scoped name.
|
||||||
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
|
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
|
||||||
@ -28,8 +29,6 @@ sealed class Statement : Node {
|
|||||||
return scope.joinToString(".")
|
return scope.joinToString(".")
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract val expensiveToInline: Boolean
|
|
||||||
|
|
||||||
fun definingBlock(): Block {
|
fun definingBlock(): Block {
|
||||||
if(this is Block)
|
if(this is Block)
|
||||||
return this
|
return this
|
||||||
@ -37,13 +36,16 @@ sealed class Statement : Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
|
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
|
||||||
override var parent: Node = ParentSentinel
|
override var parent: Node = ParentSentinel
|
||||||
override fun linkParents(parent: Node) {}
|
override fun linkParents(parent: Node) {}
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
|
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
|
||||||
override val expensiveToInline = false
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
|
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
|
||||||
@ -54,16 +56,21 @@ class Block(override val name: String,
|
|||||||
val isInLibrary: Boolean,
|
val isInLibrary: Boolean,
|
||||||
override val position: Position) : Statement(), INameScope {
|
override val position: Position) : Statement(), INameScope {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline
|
|
||||||
get() = statements.any { it.expensiveToInline }
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
statements.forEach {it.linkParents(this)}
|
statements.forEach {it.linkParents(this)}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Statement)
|
||||||
|
val idx = statements.indexOfFirst { it ===node }
|
||||||
|
statements[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Block(name=$name, address=$address, ${statements.size} statements)"
|
return "Block(name=$name, address=$address, ${statements.size} statements)"
|
||||||
@ -74,15 +81,15 @@ class Block(override val name: String,
|
|||||||
|
|
||||||
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() {
|
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
args.forEach{it.linkParents(this)}
|
args.forEach{it.linkParents(this)}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node {
|
data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node {
|
||||||
@ -91,18 +98,19 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
|
|||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
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(val name: String, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Label(name=$name, pos=$position)"
|
return "Label(name=$name, pos=$position)"
|
||||||
@ -111,15 +119,20 @@ data class Label(val name: String, override val position: Position) : Statement(
|
|||||||
|
|
||||||
open class Return(var value: Expression?, override val position: Position) : Statement() {
|
open class Return(var value: Expression?, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = value!=null && value !is NumericLiteralValue
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
value?.linkParents(this)
|
value?.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression)
|
||||||
|
value = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Return($value, pos=$position)"
|
return "Return($value, pos=$position)"
|
||||||
@ -127,36 +140,36 @@ open class Return(var value: Expression?, override val position: Position) : Sta
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ReturnFromIrq(override val position: Position) : Return(null, position) {
|
class ReturnFromIrq(override val position: Position) : Return(null, position) {
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "ReturnFromIrq(pos=$position)"
|
return "ReturnFromIrq(pos=$position)"
|
||||||
}
|
}
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
}
|
}
|
||||||
|
|
||||||
class Continue(override val position: Position) : Statement() {
|
class Continue(override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent=parent
|
this.parent=parent
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Break(override val position: Position) : Statement() {
|
class Break(override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent=parent
|
this.parent=parent
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -167,7 +180,8 @@ enum class ZeropageWish {
|
|||||||
NOT_IN_ZEROPAGE
|
NOT_IN_ZEROPAGE
|
||||||
}
|
}
|
||||||
|
|
||||||
class VarDecl(val type: VarDeclType,
|
|
||||||
|
open class VarDecl(val type: VarDeclType,
|
||||||
private val declaredDatatype: DataType,
|
private val declaredDatatype: DataType,
|
||||||
val zeropage: ZeropageWish,
|
val zeropage: ZeropageWish,
|
||||||
var arraysize: ArrayIndex?,
|
var arraysize: ArrayIndex?,
|
||||||
@ -183,9 +197,6 @@ class VarDecl(val type: VarDeclType,
|
|||||||
var structHasBeenFlattened = false // set later
|
var structHasBeenFlattened = false // set later
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override val expensiveToInline
|
|
||||||
get() = value!=null && value !is NumericLiteralValue
|
|
||||||
|
|
||||||
// prefix for literal values that are turned into a variable on the heap
|
// prefix for literal values that are turned into a variable on the heap
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -199,11 +210,20 @@ class VarDecl(val type: VarDeclType,
|
|||||||
|
|
||||||
fun createAuto(array: ArrayLiteralValue): VarDecl {
|
fun createAuto(array: ArrayLiteralValue): VarDecl {
|
||||||
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
|
||||||
val declaredType = ArrayElementTypes.getValue(array.type)
|
val declaredType = ArrayElementTypes.getValue(array.type.typeOrElse(DataType.STRUCT))
|
||||||
val arraysize = ArrayIndex.forArray(array)
|
val arraysize = ArrayIndex.forArray(array)
|
||||||
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
|
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
|
||||||
isArray = true, autogeneratedDontRemove = true, position = array.position)
|
isArray = true, autogeneratedDontRemove = true, position = array.position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun defaultZero(dt: DataType, position: Position) = when(dt) {
|
||||||
|
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
|
||||||
|
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
|
||||||
|
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
|
||||||
|
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
|
||||||
|
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
|
||||||
|
else -> throw FatalAstException("can only determine default zero value for a numeric type")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
|
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
|
||||||
@ -232,34 +252,25 @@ class VarDecl(val type: VarDeclType,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
require(replacement is Expression && node===value)
|
||||||
|
value = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
val scopedname: String by lazy { makeScopedName(name) }
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
|
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun asDefaultValueDecl(parent: Node?): VarDecl {
|
fun zeroElementValue() = defaultZero(declaredDatatype, position)
|
||||||
val constValue = when(declaredDatatype) {
|
|
||||||
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
|
|
||||||
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
|
|
||||||
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
|
|
||||||
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
|
|
||||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
|
|
||||||
else -> throw FatalAstException("can only set a default value for a numeric type")
|
|
||||||
}
|
|
||||||
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, false, position)
|
|
||||||
if(parent!=null)
|
|
||||||
decl.linkParents(parent)
|
|
||||||
return decl
|
|
||||||
}
|
|
||||||
|
|
||||||
fun flattenStructMembers(): MutableList<Statement> {
|
fun flattenStructMembers(): MutableList<Statement> {
|
||||||
val result = struct!!.statements.withIndex().map {
|
val result = struct!!.statements.withIndex().map {
|
||||||
val member = it.value as VarDecl
|
val member = it.value as VarDecl
|
||||||
val initvalue = if(value!=null) (value as StructLiteralValue).values[it.index] else null
|
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[it.index] else null
|
||||||
VarDecl(
|
VarDecl(
|
||||||
VarDeclType.VAR,
|
VarDeclType.VAR,
|
||||||
member.datatype,
|
member.datatype,
|
||||||
@ -278,6 +289,11 @@ class VarDecl(val type: VarDeclType,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a vardecl used only for subroutine parameters
|
||||||
|
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
|
||||||
|
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.NOT_IN_ZEROPAGE, null, name, null, null, false, true, position)
|
||||||
|
|
||||||
|
|
||||||
class ArrayIndex(var index: Expression, override val position: Position) : Node {
|
class ArrayIndex(var index: Expression, override val position: Position) : Node {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
|
|
||||||
@ -286,19 +302,20 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
|||||||
index.linkParents(this)
|
index.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression && node===index)
|
||||||
|
index = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun forArray(v: ArrayLiteralValue): ArrayIndex {
|
fun forArray(v: ArrayLiteralValue): ArrayIndex {
|
||||||
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
|
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun accept(visitor: IAstModifyingVisitor) {
|
fun accept(visitor: IAstVisitor) = index.accept(visitor)
|
||||||
index = index.accept(visitor)
|
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
|
||||||
}
|
|
||||||
|
|
||||||
fun accept(visitor: IAstVisitor) {
|
|
||||||
index.accept(visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return("ArrayIndex($index, pos=$position)")
|
return("ArrayIndex($index, pos=$position)")
|
||||||
@ -307,10 +324,8 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
|||||||
fun size() = (index as? NumericLiteralValue)?.number?.toInt()
|
fun size() = (index as? NumericLiteralValue)?.number?.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
open class Assignment(var target: AssignTarget, val aug_op : String?, var value: Expression, override val position: Position) : Statement() {
|
open class Assignment(var target: AssignTarget, var aug_op : String?, var value: Expression, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline
|
|
||||||
get() = value !is NumericLiteralValue
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -318,21 +333,47 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
|
|||||||
value.linkParents(this)
|
value.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===target -> target = replacement as AssignTarget
|
||||||
|
node===value -> value = replacement as Expression
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
|
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun asDesugaredNonaugmented(): Assignment {
|
||||||
|
val augmented = aug_op ?: return this
|
||||||
|
|
||||||
|
val leftOperand: Expression =
|
||||||
|
when {
|
||||||
|
target.identifier != null -> target.identifier!!
|
||||||
|
target.arrayindexed != null -> target.arrayindexed!!
|
||||||
|
target.memoryAddress != null -> DirectMemoryRead(target.memoryAddress!!.addressExpression, value.position)
|
||||||
|
else -> throw FatalAstException("strange this")
|
||||||
|
}
|
||||||
|
|
||||||
|
val assignment =
|
||||||
|
if(augmented=="setvalue") {
|
||||||
|
Assignment(target, null, value, position)
|
||||||
|
} else {
|
||||||
|
val expression = BinaryExpression(leftOperand, augmented.substringBeforeLast('='), value, position)
|
||||||
|
Assignment(target, null, expression, position)
|
||||||
|
}
|
||||||
|
assignment.linkParents(parent)
|
||||||
|
|
||||||
|
return assignment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope,
|
data class AssignTarget(var identifier: IdentifierReference?,
|
||||||
// or just a regular assignment. It may optimize the initialization step from this.
|
|
||||||
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: Expression, position: Position)
|
|
||||||
: Assignment(target, aug_op, value, position)
|
|
||||||
|
|
||||||
data class AssignTarget(val register: Register?,
|
|
||||||
var identifier: IdentifierReference?,
|
|
||||||
var arrayindexed: ArrayIndexedExpression?,
|
var arrayindexed: ArrayIndexedExpression?,
|
||||||
val memoryAddress: DirectMemoryWrite?,
|
val memoryAddress: DirectMemoryWrite?,
|
||||||
override val position: Position) : Node {
|
override val position: Position) : Node {
|
||||||
@ -345,25 +386,30 @@ data class AssignTarget(val register: Register?,
|
|||||||
memoryAddress?.linkParents(this)
|
memoryAddress?.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===identifier -> identifier = replacement as IdentifierReference
|
||||||
|
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromExpr(expr: Expression): AssignTarget {
|
fun fromExpr(expr: Expression): AssignTarget {
|
||||||
return when (expr) {
|
return when (expr) {
|
||||||
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
|
is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
|
||||||
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
|
is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
|
||||||
is ArrayIndexedExpression -> AssignTarget(null, null, expr, null, expr.position)
|
is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
|
||||||
is DirectMemoryRead -> AssignTarget(null, null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
|
|
||||||
else -> throw FatalAstException("invalid expression object $expr")
|
else -> throw FatalAstException("invalid expression object $expr")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
|
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
|
||||||
if(register!=null)
|
|
||||||
return InferredTypes.knownFor(DataType.UBYTE)
|
|
||||||
|
|
||||||
if(identifier!=null) {
|
if(identifier!=null) {
|
||||||
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
|
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
|
||||||
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
|
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
|
||||||
@ -381,8 +427,13 @@ data class AssignTarget(val register: Register?,
|
|||||||
|
|
||||||
infix fun isSameAs(value: Expression): Boolean {
|
infix fun isSameAs(value: Expression): Boolean {
|
||||||
return when {
|
return when {
|
||||||
this.memoryAddress!=null -> false
|
this.memoryAddress!=null -> {
|
||||||
this.register!=null -> value is RegisterExpr && value.register==register
|
// if the target is a memory write, and the value is a memory read, they're the same if the address matches
|
||||||
|
if(value is DirectMemoryRead)
|
||||||
|
this.memoryAddress.addressExpression isSameAs value.addressExpression
|
||||||
|
else
|
||||||
|
false
|
||||||
|
}
|
||||||
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
|
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
|
||||||
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
||||||
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
|
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
|
||||||
@ -396,8 +447,6 @@ data class AssignTarget(val register: Register?,
|
|||||||
fun isSameAs(other: AssignTarget, program: Program): Boolean {
|
fun isSameAs(other: AssignTarget, program: Program): Boolean {
|
||||||
if(this===other)
|
if(this===other)
|
||||||
return true
|
return true
|
||||||
if(this.register!=null && other.register!=null)
|
|
||||||
return this.register==other.register
|
|
||||||
if(this.identifier!=null && other.identifier!=null)
|
if(this.identifier!=null && other.identifier!=null)
|
||||||
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
|
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
|
||||||
if(this.memoryAddress!=null && other.memoryAddress!=null) {
|
if(this.memoryAddress!=null && other.memoryAddress!=null) {
|
||||||
@ -416,8 +465,6 @@ data class AssignTarget(val register: Register?,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isNotMemory(namespace: INameScope): Boolean {
|
fun isNotMemory(namespace: INameScope): Boolean {
|
||||||
if(this.register!=null)
|
|
||||||
return true
|
|
||||||
if(this.memoryAddress!=null)
|
if(this.memoryAddress!=null)
|
||||||
return false
|
return false
|
||||||
if(this.arrayindexed!=null) {
|
if(this.arrayindexed!=null) {
|
||||||
@ -436,15 +483,20 @@ data class AssignTarget(val register: Register?,
|
|||||||
|
|
||||||
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
|
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
target.linkParents(this)
|
target.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is AssignTarget && node===target)
|
||||||
|
target = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
|
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
|
||||||
@ -456,15 +508,15 @@ class Jump(val address: Int?,
|
|||||||
val generatedLabel: String?, // used in code generation scenarios
|
val generatedLabel: String?, // used in code generation scenarios
|
||||||
override val position: Position) : Statement() {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
identifier?.linkParents(this)
|
identifier?.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
|
return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
|
||||||
@ -476,8 +528,6 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
|||||||
val void: Boolean,
|
val void: Boolean,
|
||||||
override val position: Position) : Statement(), IFunctionCall {
|
override val position: Position) : Statement(), IFunctionCall {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline
|
|
||||||
get() = args.any { it !is NumericLiteralValue }
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -485,8 +535,18 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
|||||||
args.forEach { it.linkParents(this) }
|
args.forEach { it.linkParents(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
if(node===target)
|
||||||
|
target = replacement as IdentifierReference
|
||||||
|
else {
|
||||||
|
val idx = args.indexOfFirst { it===node }
|
||||||
|
args[idx] = replacement as Expression
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "FunctionCallStatement(target=$target, pos=$position)"
|
return "FunctionCallStatement(target=$target, pos=$position)"
|
||||||
@ -495,22 +555,20 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
|||||||
|
|
||||||
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
|
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = true
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnonymousScope(override var statements: MutableList<Statement>,
|
class AnonymousScope(override var statements: MutableList<Statement>,
|
||||||
override val position: Position) : INameScope, Statement() {
|
override val position: Position) : INameScope, Statement() {
|
||||||
override val name: String
|
override val name: String
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline
|
|
||||||
get() = statements.any { it.expensiveToInline }
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var sequenceNumber = 1
|
private var sequenceNumber = 1
|
||||||
@ -526,28 +584,27 @@ class AnonymousScope(override var statements: MutableList<Statement>,
|
|||||||
statements.forEach { it.linkParents(this) }
|
statements.forEach { it.linkParents(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Statement)
|
||||||
|
val idx = statements.indexOfFirst { it===node }
|
||||||
|
statements[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
class NopStatement(override val position: Position): Statement() {
|
class NopStatement(override val position: Position): Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = false
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
companion object {
|
|
||||||
fun insteadOf(stmt: Statement): NopStatement {
|
|
||||||
val nop = NopStatement(stmt.position)
|
|
||||||
nop.parent = stmt.parent
|
|
||||||
return nop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// the subroutine class covers both the normal user-defined subroutines,
|
// the subroutine class covers both the normal user-defined subroutines,
|
||||||
@ -558,20 +615,14 @@ class Subroutine(override val name: String,
|
|||||||
val returntypes: List<DataType>,
|
val returntypes: List<DataType>,
|
||||||
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||||
val asmClobbers: Set<Register>,
|
val asmClobbers: Set<CpuRegister>,
|
||||||
val asmAddress: Int?,
|
val asmAddress: Int?,
|
||||||
val isAsmSubroutine: Boolean,
|
val isAsmSubroutine: Boolean,
|
||||||
override var statements: MutableList<Statement>,
|
override var statements: MutableList<Statement>,
|
||||||
override val position: Position) : Statement(), INameScope {
|
override val position: Position) : Statement(), INameScope {
|
||||||
|
|
||||||
var keepAlways: Boolean = false
|
var keepAlways: Boolean = false
|
||||||
override val expensiveToInline
|
|
||||||
get() = statements.any { it.expensiveToInline }
|
|
||||||
|
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
val calledBy = mutableListOf<Node>()
|
|
||||||
val calls = mutableSetOf<Subroutine>()
|
|
||||||
|
|
||||||
val scopedname: String by lazy { makeScopedName(name) }
|
val scopedname: String by lazy { makeScopedName(name) }
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
@ -580,13 +631,22 @@ class Subroutine(override val name: String,
|
|||||||
statements.forEach { it.linkParents(this) }
|
statements.forEach { it.linkParents(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Statement)
|
||||||
|
val idx = statements.indexOfFirst { it===node }
|
||||||
|
statements[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
|
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||||
|
|
||||||
fun amountOfRtsInAsm(): Int = statements
|
fun amountOfRtsInAsm(): Int = statements
|
||||||
.asSequence()
|
.asSequence()
|
||||||
.filter { it is InlineAssembly }
|
.filter { it is InlineAssembly }
|
||||||
@ -594,6 +654,7 @@ class Subroutine(override val name: String,
|
|||||||
.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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
open class SubroutineParameter(val name: String,
|
open class SubroutineParameter(val name: String,
|
||||||
val type: DataType,
|
val type: DataType,
|
||||||
override val position: Position) : Node {
|
override val position: Position) : Node {
|
||||||
@ -602,6 +663,10 @@ open class SubroutineParameter(val name: String,
|
|||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
throw FatalAstException("can't replace anything in a subroutineparameter node")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IfStatement(var condition: Expression,
|
class IfStatement(var condition: Expression,
|
||||||
@ -609,8 +674,6 @@ class IfStatement(var condition: Expression,
|
|||||||
var elsepart: AnonymousScope,
|
var elsepart: AnonymousScope,
|
||||||
override val position: Position) : Statement() {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline: Boolean
|
|
||||||
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -619,8 +682,19 @@ class IfStatement(var condition: Expression,
|
|||||||
elsepart.linkParents(this)
|
elsepart.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===condition -> condition = replacement as Expression
|
||||||
|
node===truepart -> truepart = replacement as AnonymousScope
|
||||||
|
node===elsepart -> elsepart = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class BranchStatement(var condition: BranchCondition,
|
class BranchStatement(var condition: BranchCondition,
|
||||||
@ -628,8 +702,6 @@ class BranchStatement(var condition: BranchCondition,
|
|||||||
var elsepart: AnonymousScope,
|
var elsepart: AnonymousScope,
|
||||||
override val position: Position) : Statement() {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline: Boolean
|
|
||||||
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -637,38 +709,57 @@ class BranchStatement(var condition: BranchCondition,
|
|||||||
elsepart.linkParents(this)
|
elsepart.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===truepart -> truepart = replacement as AnonymousScope
|
||||||
|
node===elsepart -> elsepart = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ForLoop(val loopRegister: Register?,
|
class ForLoop(var loopVar: IdentifierReference,
|
||||||
var loopVar: IdentifierReference?,
|
|
||||||
var iterable: Expression,
|
var iterable: Expression,
|
||||||
var body: AnonymousScope,
|
var body: AnonymousScope,
|
||||||
override val position: Position) : Statement() {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = true
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent=parent
|
this.parent=parent
|
||||||
loopVar?.linkParents(this)
|
loopVar.linkParents(this)
|
||||||
iterable.linkParents(this)
|
iterable.linkParents(this)
|
||||||
body.linkParents(this)
|
body.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===loopVar -> loopVar = replacement as IdentifierReference
|
||||||
|
node===iterable -> iterable = replacement as Expression
|
||||||
|
node===body -> body = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
|
return "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loopVarDt(program: Program) = loopVar.inferType(program)
|
||||||
}
|
}
|
||||||
|
|
||||||
class WhileLoop(var condition: Expression,
|
class WhileLoop(var condition: Expression,
|
||||||
var body: AnonymousScope,
|
var body: AnonymousScope,
|
||||||
override val position: Position) : Statement() {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = true
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -676,15 +767,45 @@ class WhileLoop(var condition: Expression,
|
|||||||
body.linkParents(this)
|
body.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===condition -> condition = replacement as Expression
|
||||||
|
node===body -> body = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
class RepeatLoop(var body: AnonymousScope,
|
class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override val position: Position) : Statement() {
|
||||||
|
override lateinit var parent: Node
|
||||||
|
|
||||||
|
override fun linkParents(parent: Node) {
|
||||||
|
this.parent = parent
|
||||||
|
iterations?.linkParents(this)
|
||||||
|
body.linkParents(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===iterations -> iterations = replacement as Expression
|
||||||
|
node===body -> body = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
class UntilLoop(var body: AnonymousScope,
|
||||||
var untilCondition: Expression,
|
var untilCondition: Expression,
|
||||||
override val position: Position) : Statement() {
|
override val position: Position) : Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline = true
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -692,15 +813,23 @@ class RepeatLoop(var body: AnonymousScope,
|
|||||||
body.linkParents(this)
|
body.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
when {
|
||||||
|
node===untilCondition -> untilCondition = replacement as Expression
|
||||||
|
node===body -> body = replacement as AnonymousScope
|
||||||
|
else -> throw FatalAstException("invalid replace")
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
class WhenStatement(var condition: Expression,
|
class WhenStatement(var condition: Expression,
|
||||||
var choices: MutableList<WhenChoice>,
|
var choices: MutableList<WhenChoice>,
|
||||||
override val position: Position): Statement() {
|
override val position: Position): Statement() {
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline: Boolean = true
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
@ -708,6 +837,16 @@ class WhenStatement(var condition: Expression,
|
|||||||
choices.forEach { it.linkParents(this) }
|
choices.forEach { it.linkParents(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
if(node===condition)
|
||||||
|
condition = replacement as Expression
|
||||||
|
else {
|
||||||
|
val idx = choices.withIndex().find { it.value===node }!!.index
|
||||||
|
choices[idx] = replacement as WhenChoice
|
||||||
|
}
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
|
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
|
||||||
// only gives sensible results when the choices are all valid (constant integers)
|
// only gives sensible results when the choices are all valid (constant integers)
|
||||||
val result = mutableListOf<Pair<List<Int>?, WhenChoice>>()
|
val result = mutableListOf<Pair<List<Int>?, WhenChoice>>()
|
||||||
@ -726,7 +865,7 @@ class WhenStatement(var condition: Expression,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
class WhenChoice(var values: List<Expression>?, // if null, this is the 'else' part
|
class WhenChoice(var values: List<Expression>?, // if null, this is the 'else' part
|
||||||
@ -740,12 +879,18 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
|
|||||||
this.parent = parent
|
this.parent = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is AnonymousScope && node===statements)
|
||||||
|
statements = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "Choice($values at $position)"
|
return "Choice($values at $position)"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -754,18 +899,24 @@ class StructDecl(override val name: String,
|
|||||||
override val position: Position): Statement(), INameScope {
|
override val position: Position): Statement(), INameScope {
|
||||||
|
|
||||||
override lateinit var parent: Node
|
override lateinit var parent: Node
|
||||||
override val expensiveToInline: Boolean = true
|
|
||||||
|
|
||||||
override fun linkParents(parent: Node) {
|
override fun linkParents(parent: Node) {
|
||||||
this.parent = parent
|
this.parent = parent
|
||||||
this.statements.forEach { it.linkParents(this) }
|
this.statements.forEach { it.linkParents(this) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Statement)
|
||||||
|
val idx = statements.indexOfFirst { it===node }
|
||||||
|
statements[idx] = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
val numberOfElements: Int
|
val numberOfElements: Int
|
||||||
get() = this.statements.size
|
get() = this.statements.size
|
||||||
|
|
||||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
|
|
||||||
fun nameOfFirstMember() = (statements.first() as VarDecl).name
|
fun nameOfFirstMember() = (statements.first() as VarDecl).name
|
||||||
}
|
}
|
||||||
@ -778,10 +929,16 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
|
|||||||
this.addressExpression.linkParents(this)
|
this.addressExpression.linkParents(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||||
|
require(replacement is Expression && node===addressExpression)
|
||||||
|
addressExpression = replacement
|
||||||
|
replacement.parent = this
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "DirectMemoryWrite($addressExpression)"
|
return "DirectMemoryWrite($addressExpression)"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||||
}
|
}
|
||||||
|
106
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
106
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package prog8.compiler
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstModification
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
|
||||||
|
|
||||||
|
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||||
|
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
|
if (decl.value == null && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||||
|
// a numeric vardecl without an initial value is initialized with zero.
|
||||||
|
decl.value = decl.zeroElementValue()
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
|
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||||
|
val sub = scope.definingSubroutine()
|
||||||
|
if (sub != null) {
|
||||||
|
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
||||||
|
var conflicts = false
|
||||||
|
decls.forEach {
|
||||||
|
val existing = existingVariables[it.name]
|
||||||
|
if (existing != null) {
|
||||||
|
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
|
||||||
|
conflicts = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!conflicts) {
|
||||||
|
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
|
||||||
|
return 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, null, initValue, it.position)
|
||||||
|
initValue.parent = assign
|
||||||
|
IAstModification.InsertFirst(assign, scope)
|
||||||
|
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
|
||||||
|
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
|
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel 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)
|
||||||
|
if (subroutine.asmAddress == null
|
||||||
|
&& subroutine.statements.isNotEmpty()
|
||||||
|
&& subroutine.amountOfRtsInAsm() == 0
|
||||||
|
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||||
|
&& subroutine.statements.last() !is Subroutine) {
|
||||||
|
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
|
||||||
|
val outerScope = subroutine.definingScope()
|
||||||
|
val outerStatements = outerScope.statements
|
||||||
|
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||||
|
if (subroutineStmtIdx > 0
|
||||||
|
&& outerStatements[subroutineStmtIdx - 1] !is Jump
|
||||||
|
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
|
||||||
|
&& outerStatements[subroutineStmtIdx - 1] !is Return
|
||||||
|
&& outerScope !is Block) {
|
||||||
|
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
// see if we can remove superfluous typecasts (outside of expressions)
|
||||||
|
// such as casting byte<->ubyte, word<->uword
|
||||||
|
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
|
||||||
|
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||||
|
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||||
|
if(typecast.parent !is Expression) {
|
||||||
|
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(sourceDt in PassByReferenceDatatypes) {
|
||||||
|
if(typecast.type==DataType.UWORD) {
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
typecast,
|
||||||
|
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
}
|
@ -5,12 +5,12 @@ import prog8.ast.Program
|
|||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.statements.Directive
|
import prog8.ast.statements.Directive
|
||||||
import prog8.compiler.target.CompilationTarget
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
import prog8.optimizer.UnusedCodeRemover
|
||||||
import prog8.optimizer.constantFold
|
import prog8.optimizer.constantFold
|
||||||
import prog8.optimizer.optimizeStatements
|
import prog8.optimizer.optimizeStatements
|
||||||
import prog8.optimizer.simplifyExpressions
|
import prog8.optimizer.simplifyExpressions
|
||||||
|
import prog8.parser.ModuleImporter
|
||||||
import prog8.parser.ParsingFailedError
|
import prog8.parser.ParsingFailedError
|
||||||
import prog8.parser.importLibraryModule
|
|
||||||
import prog8.parser.importModule
|
|
||||||
import prog8.parser.moduleName
|
import prog8.parser.moduleName
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.system.measureTimeMillis
|
||||||
@ -26,89 +26,31 @@ fun compileProgram(filepath: Path,
|
|||||||
optimize: Boolean,
|
optimize: Boolean,
|
||||||
writeAssembly: Boolean,
|
writeAssembly: Boolean,
|
||||||
outputDir: Path): CompilationResult {
|
outputDir: Path): CompilationResult {
|
||||||
|
var programName = ""
|
||||||
lateinit var programAst: Program
|
lateinit var programAst: Program
|
||||||
var programName: String? = null
|
lateinit var importedFiles: List<Path>
|
||||||
|
val errors = ErrorReporter()
|
||||||
var importedFiles: List<Path> = emptyList()
|
|
||||||
var success=false
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val totalTime = measureTimeMillis {
|
val totalTime = measureTimeMillis {
|
||||||
// import main module and everything it needs
|
// import main module and everything it needs
|
||||||
println("Parsing...")
|
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
|
||||||
programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
programAst = ast
|
||||||
importModule(programAst, filepath)
|
importedFiles = imported
|
||||||
|
processAst(programAst, errors, compilationOptions)
|
||||||
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
|
if (optimize)
|
||||||
|
optimizeAst(programAst, errors)
|
||||||
val compilerOptions = determineCompilationOptions(programAst)
|
postprocessAst(programAst, errors, compilationOptions)
|
||||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
|
||||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
|
||||||
|
|
||||||
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
|
|
||||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
|
|
||||||
importLibraryModule(programAst, "c64lib")
|
|
||||||
importLibraryModule(programAst, "c64utils")
|
|
||||||
}
|
|
||||||
|
|
||||||
// always import prog8lib and math
|
|
||||||
importLibraryModule(programAst, "math")
|
|
||||||
importLibraryModule(programAst, "prog8lib")
|
|
||||||
|
|
||||||
|
|
||||||
// perform initial syntax checks and constant folding
|
|
||||||
println("Syntax check...")
|
|
||||||
val time1 = measureTimeMillis {
|
|
||||||
programAst.checkIdentifiers()
|
|
||||||
}
|
|
||||||
|
|
||||||
//println(" time1: $time1")
|
|
||||||
val time2 = measureTimeMillis {
|
|
||||||
programAst.constantFold()
|
|
||||||
}
|
|
||||||
//println(" time2: $time2")
|
|
||||||
val time3 = measureTimeMillis {
|
|
||||||
programAst.removeNopsFlattenAnonScopes()
|
|
||||||
programAst.reorderStatements()
|
|
||||||
programAst.addTypecasts()
|
|
||||||
}
|
|
||||||
//println(" time3: $time3")
|
|
||||||
val time4 = measureTimeMillis {
|
|
||||||
programAst.checkValid(compilerOptions) // check if tree is valid
|
|
||||||
}
|
|
||||||
//println(" time4: $time4")
|
|
||||||
|
|
||||||
programAst.checkIdentifiers()
|
|
||||||
if (optimize) {
|
|
||||||
// optimize the parse tree
|
|
||||||
println("Optimizing...")
|
|
||||||
while (true) {
|
|
||||||
// keep optimizing expressions and statements until no more steps remain
|
|
||||||
val optsDone1 = programAst.simplifyExpressions()
|
|
||||||
val optsDone2 = programAst.optimizeStatements()
|
|
||||||
if (optsDone1 + optsDone2 == 0)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
programAst.addTypecasts()
|
|
||||||
programAst.removeNopsFlattenAnonScopes()
|
|
||||||
programAst.checkValid(compilerOptions) // check if final tree is valid
|
|
||||||
programAst.checkRecursion() // check if there are recursive subroutine calls
|
|
||||||
|
|
||||||
// printAst(programAst)
|
// printAst(programAst)
|
||||||
|
|
||||||
if(writeAssembly) {
|
if(writeAssembly)
|
||||||
// asm generation directly from the Ast, no need for intermediate code
|
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
|
||||||
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
|
|
||||||
programAst.anonscopeVarsCleanup()
|
|
||||||
val assembly = CompilationTarget.asmGenerator(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize)
|
|
||||||
assembly.assemble(compilerOptions)
|
|
||||||
programName = assembly.name
|
|
||||||
}
|
|
||||||
success = true
|
|
||||||
}
|
}
|
||||||
|
System.out.flush()
|
||||||
|
System.err.flush()
|
||||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||||
|
return CompilationResult(true, programAst, programName, importedFiles)
|
||||||
|
|
||||||
} catch (px: ParsingFailedError) {
|
} catch (px: ParsingFailedError) {
|
||||||
System.err.print("\u001b[91m") // bright red
|
System.err.print("\u001b[91m") // bright red
|
||||||
@ -131,16 +73,35 @@ fun compileProgram(filepath: Path,
|
|||||||
System.out.flush()
|
System.out.flush()
|
||||||
throw x
|
throw x
|
||||||
}
|
}
|
||||||
return CompilationResult(success, programAst, programName ?: "", importedFiles)
|
|
||||||
|
return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun printAst(programAst: Program) {
|
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
|
||||||
println()
|
println("Parsing...")
|
||||||
val printer = AstToSourceCode(::print, programAst)
|
val importer = ModuleImporter(errors)
|
||||||
printer.visit(programAst)
|
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||||
println()
|
importer.importModule(programAst, filepath)
|
||||||
}
|
errors.handle()
|
||||||
|
|
||||||
|
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
|
||||||
|
|
||||||
|
val compilerOptions = determineCompilationOptions(programAst)
|
||||||
|
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||||
|
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||||
|
|
||||||
|
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
|
||||||
|
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
|
||||||
|
importer.importLibraryModule(programAst, "c64lib")
|
||||||
|
importer.importLibraryModule(programAst, "c64utils")
|
||||||
|
}
|
||||||
|
|
||||||
|
// always import prog8lib and math
|
||||||
|
importer.importLibraryModule(programAst, "math")
|
||||||
|
importer.importLibraryModule(programAst, "prog8lib")
|
||||||
|
errors.handle()
|
||||||
|
return Triple(programAst, compilerOptions, importedFiles)
|
||||||
|
}
|
||||||
|
|
||||||
private fun determineCompilationOptions(program: Program): CompilationOptions {
|
private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||||
val mainModule = program.modules.first()
|
val mainModule = program.modules.first()
|
||||||
@ -177,3 +138,79 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
|||||||
zpType, zpReserved, floatsEnabled
|
zpType, zpReserved, floatsEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
|
// perform initial syntax checks and processings
|
||||||
|
println("Processing...")
|
||||||
|
programAst.checkIdentifiers(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.constantFold(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.reorderStatements()
|
||||||
|
programAst.addTypecasts(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.variousCleanups()
|
||||||
|
programAst.checkValid(compilerOptions, errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.checkIdentifiers(errors)
|
||||||
|
errors.handle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
|
||||||
|
// optimize the parse tree
|
||||||
|
println("Optimizing...")
|
||||||
|
while (true) {
|
||||||
|
// keep optimizing expressions and statements until no more steps remain
|
||||||
|
val optsDone1 = programAst.simplifyExpressions()
|
||||||
|
val optsDone2 = programAst.optimizeStatements(errors)
|
||||||
|
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away:
|
||||||
|
errors.handle()
|
||||||
|
if (optsDone1 + optsDone2 == 0)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
val remover = UnusedCodeRemover()
|
||||||
|
remover.visit(programAst)
|
||||||
|
remover.applyModifications()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||||
|
programAst.transformAssignments(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.addTypecasts(errors)
|
||||||
|
errors.handle()
|
||||||
|
programAst.variousCleanups()
|
||||||
|
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
|
||||||
|
errors.handle()
|
||||||
|
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
|
||||||
|
errors.handle()
|
||||||
|
programAst.verifyFunctionArgTypes()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
|
||||||
|
optimize: Boolean, compilerOptions: CompilationOptions): String {
|
||||||
|
// asm generation directly from the Ast,
|
||||||
|
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
|
||||||
|
programAst.processAstBeforeAsmGeneration(errors)
|
||||||
|
errors.handle()
|
||||||
|
|
||||||
|
// printAst(programAst)
|
||||||
|
|
||||||
|
val assembly = CompilationTarget.asmGenerator(
|
||||||
|
programAst,
|
||||||
|
errors,
|
||||||
|
zeropage,
|
||||||
|
compilerOptions,
|
||||||
|
outputDir).compileToAssembly(optimize)
|
||||||
|
assembly.assemble(compilerOptions)
|
||||||
|
errors.handle()
|
||||||
|
return assembly.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun printAst(programAst: Program) {
|
||||||
|
println()
|
||||||
|
val printer = AstToSourceCode(::print, programAst)
|
||||||
|
printer.visit(programAst)
|
||||||
|
println()
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
|
|
||||||
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||||
|
|
||||||
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
|
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
|
||||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
|
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
|
||||||
|
|
||||||
if(options.zeropage==ZeropageType.DONTUSE)
|
if(options.zeropage==ZeropageType.DONTUSE)
|
||||||
@ -28,9 +28,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
|||||||
DataType.FLOAT -> {
|
DataType.FLOAT -> {
|
||||||
if (options.floats) {
|
if (options.floats) {
|
||||||
if(position!=null)
|
if(position!=null)
|
||||||
printWarning("allocated a large value (float) in zeropage", position)
|
errors.warn("allocated a large value (float) in zeropage", position)
|
||||||
else
|
else
|
||||||
printWarning("$scopedname: allocated a large value (float) in zeropage")
|
errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY)
|
||||||
5
|
5
|
||||||
} else throw CompilerException("floating point option not enabled")
|
} else throw CompilerException("floating point option not enabled")
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
package prog8.compiler.target
|
package prog8.compiler.target
|
||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.ErrorReporter
|
||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.Zeropage
|
import prog8.compiler.Zeropage
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
internal interface CompilationTarget {
|
internal interface CompilationTarget {
|
||||||
companion object {
|
companion object {
|
||||||
lateinit var name: String
|
lateinit var name: String
|
||||||
lateinit var machine: IMachineDefinition
|
lateinit var machine: IMachineDefinition
|
||||||
lateinit var encodeString: (str: String) -> List<Short>
|
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
|
||||||
lateinit var decodeString: (bytes: List<Short>) -> String
|
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
|
||||||
lateinit var asmGenerator: (Program, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
|
lateinit var asmGenerator: (Program, ErrorReporter, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ internal interface IAssemblyGenerator {
|
|||||||
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
|
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal const val generatedLabelPrefix = "_prog8_label_"
|
||||||
|
|
||||||
internal interface IAssemblyProgram {
|
internal interface IAssemblyProgram {
|
||||||
val name: String
|
val name: String
|
||||||
fun assemble(options: CompilationOptions)
|
fun assemble(options: CompilationOptions)
|
||||||
|
@ -3,10 +3,11 @@ package prog8.compiler.target.c64
|
|||||||
import prog8.compiler.CompilationOptions
|
import prog8.compiler.CompilationOptions
|
||||||
import prog8.compiler.OutputType
|
import prog8.compiler.OutputType
|
||||||
import prog8.compiler.target.IAssemblyProgram
|
import prog8.compiler.target.IAssemblyProgram
|
||||||
|
import prog8.compiler.target.generatedLabelPrefix
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class AssemblyProgram(override val name: String, outputDir: Path): IAssemblyProgram {
|
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
|
||||||
private val assemblyFile = outputDir.resolve("$name.asm")
|
private val assemblyFile = outputDir.resolve("$name.asm")
|
||||||
private val prgFile = outputDir.resolve("$name.prg")
|
private val prgFile = outputDir.resolve("$name.prg")
|
||||||
private val binFile = outputDir.resolve("$name.bin")
|
private val binFile = outputDir.resolve("$name.bin")
|
||||||
@ -18,7 +19,7 @@ class AssemblyProgram(override val name: String, outputDir: Path): IAssemblyProg
|
|||||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
|
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
|
||||||
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
||||||
|
|
||||||
val outFile = when(options.output) {
|
val outFile = when (options.output) {
|
||||||
OutputType.PRG -> {
|
OutputType.PRG -> {
|
||||||
command.add("--cbm-prg")
|
command.add("--cbm-prg")
|
||||||
println("\nCreating C-64 prg.")
|
println("\nCreating C-64 prg.")
|
||||||
@ -34,27 +35,39 @@ class AssemblyProgram(override val name: String, outputDir: Path): IAssemblyProg
|
|||||||
|
|
||||||
val proc = ProcessBuilder(command).inheritIO().start()
|
val proc = ProcessBuilder(command).inheritIO().start()
|
||||||
val result = proc.waitFor()
|
val result = proc.waitFor()
|
||||||
if(result!=0) {
|
if (result != 0) {
|
||||||
System.err.println("assembler failed with returncode $result")
|
System.err.println("assembler failed with returncode $result")
|
||||||
exitProcess(result)
|
exitProcess(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeGeneratedLabelsFromMonlist()
|
||||||
generateBreakpointList()
|
generateBreakpointList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeGeneratedLabelsFromMonlist() {
|
||||||
|
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
|
||||||
|
val lines = viceMonListFile.toFile().readLines()
|
||||||
|
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
||||||
|
for (line in lines) {
|
||||||
|
if(pattern.matchEntire(line)==null)
|
||||||
|
it.write(line+"\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun generateBreakpointList() {
|
private fun generateBreakpointList() {
|
||||||
// builds list of breakpoints, appends to monitor list file
|
// builds list of breakpoints, appends to monitor list file
|
||||||
val breakpoints = mutableListOf<String>()
|
val breakpoints = mutableListOf<String>()
|
||||||
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them
|
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
|
||||||
for(line in viceMonListFile.toFile().readLines()) {
|
for (line in viceMonListFile.toFile().readLines()) {
|
||||||
val match = pattern.matchEntire(line)
|
val match = pattern.matchEntire(line)
|
||||||
if(match!=null)
|
if (match != null)
|
||||||
breakpoints.add("break \$" + match.groupValues[1])
|
breakpoints.add("break \$" + match.groupValues[1])
|
||||||
}
|
}
|
||||||
val num = breakpoints.size
|
val num = breakpoints.size
|
||||||
breakpoints.add(0, "; vice monitor breakpoint list now follows")
|
breakpoints.add(0, "; vice monitor breakpoint list now follows")
|
||||||
breakpoints.add(1, "; $num breakpoints have been defined")
|
breakpoints.add(1, "; $num breakpoints have been defined")
|
||||||
breakpoints.add(2, "del")
|
breakpoints.add(2, "del")
|
||||||
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n")+"\n")
|
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,6 @@ import prog8.compiler.CompilerException
|
|||||||
import prog8.compiler.Zeropage
|
import prog8.compiler.Zeropage
|
||||||
import prog8.compiler.ZeropageType
|
import prog8.compiler.ZeropageType
|
||||||
import prog8.compiler.target.IMachineDefinition
|
import prog8.compiler.target.IMachineDefinition
|
||||||
import java.awt.Color
|
|
||||||
import java.awt.image.BufferedImage
|
|
||||||
import javax.imageio.ImageIO
|
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
@ -177,90 +174,4 @@ object C64MachineDefinition: IMachineDefinition {
|
|||||||
return if (sign) -result else result
|
return if (sign) -result else result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Charset {
|
|
||||||
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
|
|
||||||
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
|
|
||||||
|
|
||||||
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
|
|
||||||
|
|
||||||
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
|
||||||
transparent.createGraphics().drawImage(img, 0, 0, null)
|
|
||||||
|
|
||||||
val black = Color(0, 0, 0).rgb
|
|
||||||
val nopixel = Color(0, 0, 0, 0).rgb
|
|
||||||
for (y in 0 until transparent.height) {
|
|
||||||
for (x in 0 until transparent.width) {
|
|
||||||
val col = transparent.getRGB(x, y)
|
|
||||||
if (col == black)
|
|
||||||
transparent.setRGB(x, y, nopixel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val numColumns = transparent.width / 8
|
|
||||||
val charImages = (0..255).map {
|
|
||||||
val charX = it % numColumns
|
|
||||||
val charY = it / numColumns
|
|
||||||
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
|
|
||||||
}
|
|
||||||
return charImages.toTypedArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
val normalChars = scanChars(normalImg)
|
|
||||||
val shiftedChars = scanChars(shiftedImg)
|
|
||||||
|
|
||||||
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
|
|
||||||
|
|
||||||
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
|
|
||||||
val colorIdx = (color % colorPalette.size).toShort()
|
|
||||||
val chars = coloredNormalChars[colorIdx]
|
|
||||||
if (chars != null)
|
|
||||||
return chars[screenCode.toInt()]
|
|
||||||
|
|
||||||
val coloredChars = mutableListOf<BufferedImage>()
|
|
||||||
val transparent = Color(0, 0, 0, 0).rgb
|
|
||||||
val rgb = colorPalette[colorIdx.toInt()].rgb
|
|
||||||
for (c in normalChars) {
|
|
||||||
val colored = c.copy()
|
|
||||||
for (y in 0 until colored.height)
|
|
||||||
for (x in 0 until colored.width) {
|
|
||||||
if (colored.getRGB(x, y) != transparent) {
|
|
||||||
colored.setRGB(x, y, rgb)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
coloredChars.add(colored)
|
|
||||||
}
|
|
||||||
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
|
|
||||||
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BufferedImage.copy(): BufferedImage {
|
|
||||||
val bcopy = BufferedImage(this.width, this.height, this.type)
|
|
||||||
val g = bcopy.graphics
|
|
||||||
g.drawImage(this, 0, 0, null)
|
|
||||||
g.dispose()
|
|
||||||
return bcopy
|
|
||||||
}
|
|
||||||
|
|
||||||
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
|
||||||
Color(0x000000), // 0 = black
|
|
||||||
Color(0xFFFFFF), // 1 = white
|
|
||||||
Color(0x813338), // 2 = red
|
|
||||||
Color(0x75cec8), // 3 = cyan
|
|
||||||
Color(0x8e3c97), // 4 = purple
|
|
||||||
Color(0x56ac4d), // 5 = green
|
|
||||||
Color(0x2e2c9b), // 6 = blue
|
|
||||||
Color(0xedf171), // 7 = yellow
|
|
||||||
Color(0x8e5029), // 8 = orange
|
|
||||||
Color(0x553800), // 9 = brown
|
|
||||||
Color(0xc46c71), // 10 = light red
|
|
||||||
Color(0x4a4a4a), // 11 = dark grey
|
|
||||||
Color(0x7b7b7b), // 12 = medium grey
|
|
||||||
Color(0xa9ff9f), // 13 = light green
|
|
||||||
Color(0x706deb), // 14 = light blue
|
|
||||||
Color(0xb2b2b2) // 15 = light grey
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package prog8.compiler.target.c64.codegen
|
package prog8.compiler.target.c64.codegen
|
||||||
|
|
||||||
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.antlr.escape
|
import prog8.ast.antlr.escape
|
||||||
@ -11,20 +12,22 @@ import prog8.compiler.target.IAssemblyGenerator
|
|||||||
import prog8.compiler.target.IAssemblyProgram
|
import prog8.compiler.target.IAssemblyProgram
|
||||||
import prog8.compiler.target.c64.AssemblyProgram
|
import prog8.compiler.target.c64.AssemblyProgram
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||||
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.c64.Petscii
|
||||||
|
import prog8.compiler.target.generatedLabelPrefix
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
import prog8.functions.FunctionSignature
|
import prog8.functions.FSignature
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.ArrayDeque
|
import java.util.*
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
|
||||||
internal class AsmGen(private val program: Program,
|
internal class AsmGen(private val program: Program,
|
||||||
|
private val errors: ErrorReporter,
|
||||||
private val zeropage: Zeropage,
|
private val zeropage: Zeropage,
|
||||||
private val options: CompilationOptions,
|
private val options: CompilationOptions,
|
||||||
private val outputDir: Path): IAssemblyGenerator {
|
private val outputDir: Path): IAssemblyGenerator {
|
||||||
@ -37,10 +40,11 @@ internal class AsmGen(private val program: Program,
|
|||||||
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
|
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
|
||||||
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
|
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
|
||||||
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
|
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
|
||||||
private val assignmentAsmGen = AssignmentAsmGen(program, this)
|
private val assignmentAsmGen = AssignmentAsmGen(program, errors, this)
|
||||||
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
|
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
|
||||||
internal val loopEndLabels = ArrayDeque<String>()
|
internal val loopEndLabels = ArrayDeque<String>()
|
||||||
internal val loopContinueLabels = ArrayDeque<String>()
|
internal val loopContinueLabels = ArrayDeque<String>()
|
||||||
|
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||||
|
|
||||||
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
|
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
|
||||||
assemblyLines.clear()
|
assemblyLines.clear()
|
||||||
@ -96,11 +100,15 @@ internal class AsmGen(private val program: Program,
|
|||||||
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
|
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
|
||||||
out("+\t.word 0")
|
out("+\t.word 0")
|
||||||
out("_prog8_entrypoint\t; assembly code starts here\n")
|
out("_prog8_entrypoint\t; assembly code starts here\n")
|
||||||
|
out(" tsx")
|
||||||
|
out(" stx prog8_lib.orig_stackpointer")
|
||||||
out(" jsr prog8_lib.init_system")
|
out(" jsr prog8_lib.init_system")
|
||||||
}
|
}
|
||||||
options.output == OutputType.PRG -> {
|
options.output == OutputType.PRG -> {
|
||||||
out("; ---- program without basic sys call ----")
|
out("; ---- program without basic sys call ----")
|
||||||
out("* = ${program.actualLoadAddress.toHex()}\n")
|
out("* = ${program.actualLoadAddress.toHex()}\n")
|
||||||
|
out(" tsx")
|
||||||
|
out(" stx prog8_lib.orig_stackpointer")
|
||||||
out(" jsr prog8_lib.init_system")
|
out(" jsr prog8_lib.init_system")
|
||||||
}
|
}
|
||||||
options.output == OutputType.RAW -> {
|
options.output == OutputType.RAW -> {
|
||||||
@ -120,11 +128,10 @@ internal class AsmGen(private val program: Program,
|
|||||||
|
|
||||||
out(" ldx #\$ff\t; init estack pointer")
|
out(" ldx #\$ff\t; init estack pointer")
|
||||||
|
|
||||||
out(" ; initialize the variables in each block")
|
out(" ; initialize the variables in each block that has globals")
|
||||||
for (block in program.allBlocks()) {
|
program.allBlocks().forEach {
|
||||||
val initVarsSub = block.statements.singleOrNull { it is Subroutine && it.name == initvarsSubName }
|
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
|
||||||
if(initVarsSub!=null)
|
out(" jsr ${it.name}.prog8_init_vars")
|
||||||
out(" jsr ${block.name}.$initvarsSubName")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out(" clc")
|
out(" clc")
|
||||||
@ -137,7 +144,6 @@ internal class AsmGen(private val program: Program,
|
|||||||
out(" jmp (c64.RESET_VEC)\t; cold reset")
|
out(" jmp (c64.RESET_VEC)\t; cold reset")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out("")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun footer() {
|
private fun footer() {
|
||||||
@ -171,6 +177,21 @@ internal class AsmGen(private val program: Program,
|
|||||||
stmts.forEach { translate(it) }
|
stmts.forEach { translate(it) }
|
||||||
subroutine.forEach { translateSubroutine(it as Subroutine) }
|
subroutine.forEach { translateSubroutine(it as Subroutine) }
|
||||||
|
|
||||||
|
// if any global vars need to be initialized, generate a subroutine that does this
|
||||||
|
// it will be called from program init.
|
||||||
|
if(block in blockLevelVarInits) {
|
||||||
|
out("prog8_init_vars\t.proc\n")
|
||||||
|
blockLevelVarInits.getValue(block).forEach { decl ->
|
||||||
|
val scopedFullName = decl.makeScopedName(decl.name).split('.')
|
||||||
|
require(scopedFullName.first()==block.name)
|
||||||
|
val target = AssignTarget(IdentifierReference(scopedFullName.drop(1), decl.position), null, null, decl.position)
|
||||||
|
val assign = Assignment(target, null, decl.value!!, decl.position)
|
||||||
|
assign.linkParents(decl.parent)
|
||||||
|
assignmentAsmGen.translate(assign)
|
||||||
|
}
|
||||||
|
out(" rts\n .pend")
|
||||||
|
}
|
||||||
|
|
||||||
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
|
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +199,7 @@ internal class AsmGen(private val program: Program,
|
|||||||
|
|
||||||
internal fun makeLabel(postfix: String): String {
|
internal fun makeLabel(postfix: String): String {
|
||||||
generatedLabelSequenceNumber++
|
generatedLabelSequenceNumber++
|
||||||
return "_prog8_label_${generatedLabelSequenceNumber}_$postfix"
|
return "${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun outputSourceLine(node: Node) {
|
private fun outputSourceLine(node: Node) {
|
||||||
@ -206,8 +227,8 @@ internal class AsmGen(private val program: Program,
|
|||||||
return "$b0, $b1, $b2, $b3, $b4"
|
return "$b0, $b1, $b2, $b3, $b4"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun petscii(str: String): List<Short> {
|
private fun encode(str: String, altEncoding: Boolean): List<Short> {
|
||||||
val bytes = Petscii.encodePetscii(str, true)
|
val bytes = if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||||
return bytes.plus(0)
|
return bytes.plus(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +237,7 @@ internal class AsmGen(private val program: Program,
|
|||||||
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||||
for(variable in variables) {
|
for(variable in variables) {
|
||||||
// should NOT allocate subroutine parameters on the zero page
|
// should NOT allocate subroutine parameters on the zero page
|
||||||
val fullName = variable.scopedname
|
val fullName = variable.makeScopedName(variable.name)
|
||||||
val zpVar = allocatedZeropageVariables[fullName]
|
val zpVar = allocatedZeropageVariables[fullName]
|
||||||
if(zpVar==null) {
|
if(zpVar==null) {
|
||||||
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
|
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
|
||||||
@ -225,7 +246,9 @@ internal class AsmGen(private val program: Program,
|
|||||||
&& variable.datatype != DataType.FLOAT
|
&& variable.datatype != DataType.FLOAT
|
||||||
&& options.zeropage != ZeropageType.DONTUSE) {
|
&& options.zeropage != ZeropageType.DONTUSE) {
|
||||||
try {
|
try {
|
||||||
val address = zeropage.allocate(fullName, variable.datatype, null)
|
val errors = ErrorReporter()
|
||||||
|
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
|
||||||
|
errors.handle()
|
||||||
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
|
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
|
||||||
// make sure we add the var to the set of zpvars for this block
|
// make sure we add the var to the set of zpvars for this block
|
||||||
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
|
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
|
||||||
@ -246,8 +269,8 @@ internal class AsmGen(private val program: Program,
|
|||||||
DataType.FLOAT -> out("${decl.name}\t.byte 0,0,0,0,0 ; float")
|
DataType.FLOAT -> out("${decl.name}\t.byte 0,0,0,0,0 ; float")
|
||||||
DataType.STRUCT -> {} // is flattened
|
DataType.STRUCT -> {} // is flattened
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
val string = (decl.value as StringLiteralValue).value
|
val str = decl.value as StringLiteralValue
|
||||||
outputStringvar(decl, petscii(string))
|
outputStringvar(decl, encode(str.value, str.altEncoding))
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UB -> {
|
DataType.ARRAY_UB -> {
|
||||||
val data = makeArrayFillDataUnsigned(decl)
|
val data = makeArrayFillDataUnsigned(decl)
|
||||||
@ -290,7 +313,14 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
val array = (decl.value as ArrayLiteralValue).value
|
val array =
|
||||||
|
if(decl.value!=null)
|
||||||
|
(decl.value as ArrayLiteralValue).value
|
||||||
|
else {
|
||||||
|
// no init value, use zeros
|
||||||
|
val zero = decl.zeroElementValue()
|
||||||
|
Array(decl.arraysize!!.size()!!) { zero }
|
||||||
|
}
|
||||||
val floatFills = array.map {
|
val floatFills = array.map {
|
||||||
val number = (it as NumericLiteralValue).number
|
val number = (it as NumericLiteralValue).number
|
||||||
makeFloatFill(C64MachineDefinition.Mflpt5.fromNumber(number))
|
makeFloatFill(C64MachineDefinition.Mflpt5.fromNumber(number))
|
||||||
@ -331,7 +361,10 @@ internal class AsmGen(private val program: Program,
|
|||||||
// special treatment for string types: merge strings that are identical
|
// special treatment for string types: merge strings that are identical
|
||||||
val encodedstringVars = normalVars
|
val encodedstringVars = normalVars
|
||||||
.filter {it.datatype == DataType.STR }
|
.filter {it.datatype == DataType.STR }
|
||||||
.map { it to petscii((it.value as StringLiteralValue).value) }
|
.map {
|
||||||
|
val str = it.value as StringLiteralValue
|
||||||
|
it to encode(str.value, str.altEncoding)
|
||||||
|
}
|
||||||
.groupBy({it.second}, {it.first})
|
.groupBy({it.second}, {it.first})
|
||||||
for((encoded, variables) in encodedstringVars) {
|
for((encoded, variables) in encodedstringVars) {
|
||||||
variables.dropLast(1).forEach { out(it.name) }
|
variables.dropLast(1).forEach { out(it.name) }
|
||||||
@ -341,7 +374,7 @@ internal class AsmGen(private val program: Program,
|
|||||||
|
|
||||||
// non-string variables
|
// non-string variables
|
||||||
normalVars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
|
normalVars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
|
||||||
if(it.scopedname !in allocatedZeropageVariables)
|
if(it.makeScopedName(it.name) !in allocatedZeropageVariables)
|
||||||
vardecl2asm(it)
|
vardecl2asm(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -355,7 +388,14 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun makeArrayFillDataUnsigned(decl: VarDecl): List<String> {
|
private fun makeArrayFillDataUnsigned(decl: VarDecl): List<String> {
|
||||||
val array = (decl.value as ArrayLiteralValue).value
|
val array =
|
||||||
|
if(decl.value!=null)
|
||||||
|
(decl.value as ArrayLiteralValue).value
|
||||||
|
else {
|
||||||
|
// no array init value specified, use a list of zeros
|
||||||
|
val zero = decl.zeroElementValue()
|
||||||
|
Array(decl.arraysize!!.size()!!) { zero }
|
||||||
|
}
|
||||||
return when (decl.datatype) {
|
return when (decl.datatype) {
|
||||||
DataType.ARRAY_UB ->
|
DataType.ARRAY_UB ->
|
||||||
// byte array can never contain pointer-to types, so treat values as all integers
|
// byte array can never contain pointer-to types, so treat values as all integers
|
||||||
@ -375,17 +415,22 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun makeArrayFillDataSigned(decl: VarDecl): List<String> {
|
private fun makeArrayFillDataSigned(decl: VarDecl): List<String> {
|
||||||
val array = (decl.value as ArrayLiteralValue).value
|
val array =
|
||||||
|
if(decl.value!=null)
|
||||||
return when {
|
(decl.value as ArrayLiteralValue).value
|
||||||
decl.datatype == DataType.ARRAY_UB ->
|
else {
|
||||||
|
// no array init value specified, use a list of zeros
|
||||||
|
val zero = decl.zeroElementValue()
|
||||||
|
Array(decl.arraysize!!.size()!!) { zero }
|
||||||
|
}
|
||||||
|
return when (decl.datatype) {
|
||||||
|
DataType.ARRAY_UB ->
|
||||||
// byte array can never contain pointer-to types, so treat values as all integers
|
// byte array can never contain pointer-to types, so treat values as all integers
|
||||||
array.map {
|
array.map {
|
||||||
val number = (it as NumericLiteralValue).number.toInt()
|
val number = (it as NumericLiteralValue).number.toInt()
|
||||||
val hexnum = number.toString(16).padStart(2, '0')
|
"$"+number.toString(16).padStart(2, '0')
|
||||||
"$$hexnum"
|
|
||||||
}
|
}
|
||||||
decl.datatype == DataType.ARRAY_B ->
|
DataType.ARRAY_B ->
|
||||||
// byte array can never contain pointer-to types, so treat values as all integers
|
// byte array can never contain pointer-to types, so treat values as all integers
|
||||||
array.map {
|
array.map {
|
||||||
val number = (it as NumericLiteralValue).number.toInt()
|
val number = (it as NumericLiteralValue).number.toInt()
|
||||||
@ -395,12 +440,11 @@ internal class AsmGen(private val program: Program,
|
|||||||
else
|
else
|
||||||
"-$$hexnum"
|
"-$$hexnum"
|
||||||
}
|
}
|
||||||
decl.datatype== DataType.ARRAY_UW -> array.map {
|
DataType.ARRAY_UW -> array.map {
|
||||||
val number = (it as NumericLiteralValue).number.toInt()
|
val number = (it as NumericLiteralValue).number.toInt()
|
||||||
val hexnum = number.toString(16).padStart(4, '0')
|
"$" + number.toString(16).padStart(4, '0')
|
||||||
"$$hexnum"
|
|
||||||
}
|
}
|
||||||
decl.datatype== DataType.ARRAY_W -> array.map {
|
DataType.ARRAY_W -> array.map {
|
||||||
val number = (it as NumericLiteralValue).number.toInt()
|
val number = (it as NumericLiteralValue).number.toInt()
|
||||||
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||||
if(number>=0)
|
if(number>=0)
|
||||||
@ -518,19 +562,19 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun saveRegister(register: Register) {
|
internal fun saveRegister(register: CpuRegister) {
|
||||||
when(register) {
|
when(register) {
|
||||||
Register.A -> out(" pha")
|
CpuRegister.A -> out(" pha")
|
||||||
Register.X -> out(" txa | pha")
|
CpuRegister.X -> out(" txa | pha")
|
||||||
Register.Y -> out(" tya | pha")
|
CpuRegister.Y -> out(" tya | pha")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun restoreRegister(register: Register) {
|
internal fun restoreRegister(register: CpuRegister) {
|
||||||
when(register) {
|
when(register) {
|
||||||
Register.A -> out(" pla")
|
CpuRegister.A -> out(" pla")
|
||||||
Register.X -> out(" pla | tax")
|
CpuRegister.X -> out(" pla | tax")
|
||||||
Register.Y -> out(" pla | tay")
|
CpuRegister.Y -> out(" pla | tay")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -562,7 +606,9 @@ internal class AsmGen(private val program: Program,
|
|||||||
internal fun translate(stmt: Statement) {
|
internal fun translate(stmt: Statement) {
|
||||||
outputSourceLine(stmt)
|
outputSourceLine(stmt)
|
||||||
when(stmt) {
|
when(stmt) {
|
||||||
is VarDecl, is StructDecl, is NopStatement -> {}
|
is ParameterVarDecl -> { /* subroutine parameter vardecls don't get any special treatment here */ }
|
||||||
|
is VarDecl -> translate(stmt)
|
||||||
|
is StructDecl, is NopStatement -> {}
|
||||||
is Directive -> translate(stmt)
|
is Directive -> translate(stmt)
|
||||||
is Return -> translate(stmt)
|
is Return -> translate(stmt)
|
||||||
is Subroutine -> translateSubroutine(stmt)
|
is Subroutine -> translateSubroutine(stmt)
|
||||||
@ -596,10 +642,12 @@ internal class AsmGen(private val program: Program,
|
|||||||
is Break -> out(" jmp ${loopEndLabels.peek()}")
|
is Break -> out(" jmp ${loopEndLabels.peek()}")
|
||||||
is WhileLoop -> translate(stmt)
|
is WhileLoop -> translate(stmt)
|
||||||
is RepeatLoop -> translate(stmt)
|
is RepeatLoop -> translate(stmt)
|
||||||
|
is UntilLoop -> translate(stmt)
|
||||||
is WhenStatement -> translate(stmt)
|
is WhenStatement -> translate(stmt)
|
||||||
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
|
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
|
||||||
is AnonymousScope -> translate(stmt)
|
is AnonymousScope -> translate(stmt)
|
||||||
is Block -> throw AssemblyError("block should have been handled elsewhere")
|
is Block -> throw AssemblyError("block should have been handled elsewhere")
|
||||||
|
else -> throw AssemblyError("missing asm translation for $stmt")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,13 +673,114 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun translate(stmt: RepeatLoop) {
|
||||||
|
val repeatLabel = makeLabel("repeat")
|
||||||
|
val endLabel = makeLabel("repeatend")
|
||||||
|
val counterLabel = makeLabel("repeatcounter")
|
||||||
|
loopEndLabels.push(endLabel)
|
||||||
|
loopContinueLabels.push(repeatLabel)
|
||||||
|
|
||||||
|
when (stmt.iterations) {
|
||||||
|
null -> {
|
||||||
|
// endless loop
|
||||||
|
out(repeatLabel)
|
||||||
|
translate(stmt.body)
|
||||||
|
out(" jmp $repeatLabel")
|
||||||
|
out(endLabel)
|
||||||
|
}
|
||||||
|
is NumericLiteralValue -> {
|
||||||
|
val iterations = (stmt.iterations as NumericLiteralValue).number.toInt()
|
||||||
|
if(iterations<0 || iterations > 65536)
|
||||||
|
throw AssemblyError("invalid number of iterations")
|
||||||
|
when {
|
||||||
|
iterations == 0 -> {}
|
||||||
|
iterations <= 255 -> {
|
||||||
|
out(" lda #${iterations}")
|
||||||
|
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
out(" lda #<${iterations} | ldy #>${iterations}")
|
||||||
|
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val vardecl = (stmt.iterations as IdentifierReference).targetStatement(program.namespace) as VarDecl
|
||||||
|
val name = asmIdentifierName(stmt.iterations as IdentifierReference)
|
||||||
|
when(vardecl.datatype) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
out(" lda $name")
|
||||||
|
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
out(" lda $name | ldy $name+1")
|
||||||
|
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
translateExpression(stmt.iterations!!)
|
||||||
|
val dt = stmt.iterations!!.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
when (dt) {
|
||||||
|
in ByteDatatypes -> {
|
||||||
|
out(" inx | lda ${ESTACK_LO_HEX},x")
|
||||||
|
repeatByteCountInA(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||||
|
}
|
||||||
|
in WordDatatypes -> {
|
||||||
|
out(" inx | lda ${ESTACK_LO_HEX},x | ldy ${ESTACK_HI_HEX},x")
|
||||||
|
repeatWordCountInAY(counterLabel, repeatLabel, endLabel, stmt.body)
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("invalid loop expression datatype $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loopEndLabels.pop()
|
||||||
|
loopContinueLabels.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun repeatWordCountInAY(counterLabel: String, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||||
|
// note: A/Y must have been loaded with the number of iterations already!
|
||||||
|
out("""
|
||||||
|
sta $counterLabel
|
||||||
|
sty $counterLabel+1
|
||||||
|
$repeatLabel lda $counterLabel
|
||||||
|
bne +
|
||||||
|
lda $counterLabel+1
|
||||||
|
beq $endLabel
|
||||||
|
+ lda $counterLabel
|
||||||
|
bne +
|
||||||
|
dec $counterLabel+1
|
||||||
|
+ dec $counterLabel
|
||||||
|
""")
|
||||||
|
translate(body)
|
||||||
|
out("""
|
||||||
|
jmp $repeatLabel
|
||||||
|
$counterLabel .word 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun repeatByteCountInA(counterLabel: String, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||||
|
// note: A must have been loaded with the number of iterations already!
|
||||||
|
out("""
|
||||||
|
sta $counterLabel
|
||||||
|
$repeatLabel lda $counterLabel
|
||||||
|
beq $endLabel
|
||||||
|
dec $counterLabel""")
|
||||||
|
translate(body)
|
||||||
|
out("""
|
||||||
|
jmp $repeatLabel
|
||||||
|
$counterLabel .byte 0
|
||||||
|
$endLabel""")
|
||||||
|
}
|
||||||
|
|
||||||
private fun translate(stmt: WhileLoop) {
|
private fun translate(stmt: WhileLoop) {
|
||||||
val whileLabel = makeLabel("while")
|
val whileLabel = makeLabel("while")
|
||||||
val endLabel = makeLabel("whileend")
|
val endLabel = makeLabel("whileend")
|
||||||
loopEndLabels.push(endLabel)
|
loopEndLabels.push(endLabel)
|
||||||
loopContinueLabels.push(whileLabel)
|
loopContinueLabels.push(whileLabel)
|
||||||
out(whileLabel)
|
out(whileLabel)
|
||||||
// TODO optimize for the simple cases, can we avoid stack use?
|
|
||||||
expressionsAsmGen.translateExpression(stmt.condition)
|
expressionsAsmGen.translateExpression(stmt.condition)
|
||||||
val conditionDt = stmt.condition.inferType(program)
|
val conditionDt = stmt.condition.inferType(program)
|
||||||
if(!conditionDt.isKnown)
|
if(!conditionDt.isKnown)
|
||||||
@ -654,13 +803,12 @@ internal class AsmGen(private val program: Program,
|
|||||||
loopContinueLabels.pop()
|
loopContinueLabels.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translate(stmt: RepeatLoop) {
|
private fun translate(stmt: UntilLoop) {
|
||||||
val repeatLabel = makeLabel("repeat")
|
val repeatLabel = makeLabel("repeat")
|
||||||
val endLabel = makeLabel("repeatend")
|
val endLabel = makeLabel("repeatend")
|
||||||
loopEndLabels.push(endLabel)
|
loopEndLabels.push(endLabel)
|
||||||
loopContinueLabels.push(repeatLabel)
|
loopContinueLabels.push(repeatLabel)
|
||||||
out(repeatLabel)
|
out(repeatLabel)
|
||||||
// TODO optimize this for the simple cases, can we avoid stack use?
|
|
||||||
translate(stmt.body)
|
translate(stmt.body)
|
||||||
expressionsAsmGen.translateExpression(stmt.untilCondition)
|
expressionsAsmGen.translateExpression(stmt.untilCondition)
|
||||||
val conditionDt = stmt.untilCondition.inferType(program)
|
val conditionDt = stmt.untilCondition.inferType(program)
|
||||||
@ -726,7 +874,7 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun translate(stmt: Label) {
|
private fun translate(stmt: Label) {
|
||||||
out(stmt.name)
|
out("_${stmt.name}") // underscore prefix to make sure it's a local label
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translate(scope: AnonymousScope) {
|
private fun translate(scope: AnonymousScope) {
|
||||||
@ -765,6 +913,30 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun translate(stmt: VarDecl) {
|
||||||
|
if(stmt.value!=null && stmt.type==VarDeclType.VAR && stmt.datatype in NumericDatatypes) {
|
||||||
|
// generate an assignment statement to (re)initialize the variable's value.
|
||||||
|
// if the vardecl is not in a subroutine however, we have to initialize it globally.
|
||||||
|
if(stmt.definingSubroutine()==null) {
|
||||||
|
val block = stmt.definingBlock()
|
||||||
|
var inits = blockLevelVarInits[block]
|
||||||
|
if(inits==null) {
|
||||||
|
inits = mutableSetOf()
|
||||||
|
blockLevelVarInits[block] = inits
|
||||||
|
}
|
||||||
|
inits.add(stmt)
|
||||||
|
} else {
|
||||||
|
val next = (stmt.parent as INameScope).nextSibling(stmt)
|
||||||
|
if (next !is ForLoop || next.loopVar.nameInSource.single() != stmt.name) {
|
||||||
|
val target = AssignTarget(IdentifierReference(listOf(stmt.name), stmt.position), null, null, stmt.position)
|
||||||
|
val assign = Assignment(target, null, stmt.value!!, stmt.position)
|
||||||
|
assign.linkParents(stmt.parent)
|
||||||
|
translate(assign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun translate(stmt: Directive) {
|
private fun translate(stmt: Directive) {
|
||||||
when(stmt.directive) {
|
when(stmt.directive) {
|
||||||
"%asminclude" -> {
|
"%asminclude" -> {
|
||||||
@ -795,7 +967,14 @@ internal class AsmGen(private val program: Program,
|
|||||||
|
|
||||||
private fun getJumpTarget(jmp: Jump): String {
|
private fun getJumpTarget(jmp: Jump): String {
|
||||||
return when {
|
return when {
|
||||||
jmp.identifier!=null -> asmIdentifierName(jmp.identifier)
|
jmp.identifier!=null -> {
|
||||||
|
val target = jmp.identifier.targetStatement(program.namespace)
|
||||||
|
val asmName = asmIdentifierName(jmp.identifier)
|
||||||
|
if(target is Label)
|
||||||
|
"_$asmName" // prefix with underscore to jump to local label
|
||||||
|
else
|
||||||
|
asmName
|
||||||
|
}
|
||||||
jmp.generatedLabel!=null -> jmp.generatedLabel
|
jmp.generatedLabel!=null -> jmp.generatedLabel
|
||||||
jmp.address!=null -> jmp.address.toHex()
|
jmp.address!=null -> jmp.address.toHex()
|
||||||
else -> "????"
|
else -> "????"
|
||||||
@ -815,17 +994,11 @@ internal class AsmGen(private val program: Program,
|
|||||||
internal fun translateArrayIndexIntoA(expr: ArrayIndexedExpression) {
|
internal fun translateArrayIndexIntoA(expr: ArrayIndexedExpression) {
|
||||||
when (val index = expr.arrayspec.index) {
|
when (val index = expr.arrayspec.index) {
|
||||||
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
|
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
|
||||||
is RegisterExpr -> {
|
|
||||||
when (index.register) {
|
|
||||||
Register.A -> {}
|
|
||||||
Register.X -> out(" txa")
|
|
||||||
Register.Y -> out(" tya")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val indexName = asmIdentifierName(index)
|
val indexName = asmIdentifierName(index)
|
||||||
out(" lda $indexName")
|
out(" lda $indexName")
|
||||||
}
|
}
|
||||||
|
// TODO optimize more cases
|
||||||
else -> {
|
else -> {
|
||||||
expressionsAsmGen.translateExpression(index)
|
expressionsAsmGen.translateExpression(index)
|
||||||
out(" inx | lda $ESTACK_LO_HEX,x")
|
out(" inx | lda $ESTACK_LO_HEX,x")
|
||||||
@ -833,10 +1006,25 @@ internal class AsmGen(private val program: Program,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun translateArrayIndexIntoY(expr: ArrayIndexedExpression) {
|
||||||
|
when (val index = expr.arrayspec.index) {
|
||||||
|
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val indexName = asmIdentifierName(index)
|
||||||
|
out(" ldy $indexName")
|
||||||
|
}
|
||||||
|
// TODO optimize more cases, see translateArrayIndexIntoA
|
||||||
|
else -> {
|
||||||
|
expressionsAsmGen.translateExpression(index)
|
||||||
|
out(" inx | ldy $ESTACK_LO_HEX,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal fun translateExpression(expression: Expression) =
|
internal fun translateExpression(expression: Expression) =
|
||||||
expressionsAsmGen.translateExpression(expression)
|
expressionsAsmGen.translateExpression(expression)
|
||||||
|
|
||||||
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FunctionSignature) =
|
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
|
||||||
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
|
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
|
||||||
|
|
||||||
internal fun translateFunctionCall(functionCall: FunctionCall) =
|
internal fun translateFunctionCall(functionCall: FunctionCall) =
|
||||||
@ -863,9 +1051,12 @@ internal class AsmGen(private val program: Program,
|
|||||||
fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) =
|
fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) =
|
||||||
assignmentAsmGen.assignFromFloatVariable(target, variable)
|
assignmentAsmGen.assignFromFloatVariable(target, variable)
|
||||||
|
|
||||||
fun assignFromRegister(target: AssignTarget, register: Register) =
|
fun assignFromRegister(target: AssignTarget, register: CpuRegister) =
|
||||||
assignmentAsmGen.assignFromRegister(target, register)
|
assignmentAsmGen.assignFromRegister(target, register)
|
||||||
|
|
||||||
fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) =
|
fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) =
|
||||||
assignmentAsmGen.assignFromMemoryByte(target, address, identifier)
|
assignmentAsmGen.assignFromMemoryByte(target, address, identifier)
|
||||||
|
|
||||||
|
fun assignToRegister(reg: CpuRegister, value: Short?, identifier: IdentifierReference?) =
|
||||||
|
assignmentAsmGen.assignToRegister(reg, value, identifier)
|
||||||
}
|
}
|
||||||
|
@ -13,43 +13,45 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
|||||||
|
|
||||||
var linesByFour = getLinesBy(lines, 4)
|
var linesByFour = getLinesBy(lines, 4)
|
||||||
|
|
||||||
var removeLines = optimizeUselessStackByteWrites(linesByFour)
|
var mods = optimizeUselessStackByteWrites(linesByFour)
|
||||||
if(removeLines.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
for (i in removeLines.reversed())
|
apply(mods, lines)
|
||||||
lines.removeAt(i)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLines = optimizeIncDec(linesByFour)
|
mods = optimizeIncDec(linesByFour)
|
||||||
if(removeLines.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
for (i in removeLines.reversed())
|
apply(mods, lines)
|
||||||
lines.removeAt(i)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLines = optimizeCmpSequence(linesByFour)
|
mods = optimizeCmpSequence(linesByFour)
|
||||||
if(removeLines.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
for (i in removeLines.reversed())
|
apply(mods, lines)
|
||||||
lines.removeAt(i)
|
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
removeLines = optimizeStoreLoadSame(linesByFour)
|
mods = optimizeStoreLoadSame(linesByFour)
|
||||||
if(removeLines.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
for (i in removeLines.reversed())
|
apply(mods, lines)
|
||||||
lines.removeAt(i)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
|
numberOfOptimizations++
|
||||||
|
}
|
||||||
|
|
||||||
|
mods= optimizeJsrRts(linesByFour)
|
||||||
|
if(mods.isNotEmpty()) {
|
||||||
|
apply(mods, lines)
|
||||||
linesByFour = getLinesBy(lines, 4)
|
linesByFour = getLinesBy(lines, 4)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
|
|
||||||
var linesByFourteen = getLinesBy(lines, 14)
|
var linesByFourteen = getLinesBy(lines, 14)
|
||||||
removeLines = optimizeSameAssignments(linesByFourteen)
|
mods = optimizeSameAssignments(linesByFourteen)
|
||||||
if(removeLines.isNotEmpty()) {
|
if(mods.isNotEmpty()) {
|
||||||
for (i in removeLines.reversed())
|
apply(mods, lines)
|
||||||
lines.removeAt(i)
|
|
||||||
linesByFourteen = getLinesBy(lines, 14)
|
linesByFourteen = getLinesBy(lines, 14)
|
||||||
numberOfOptimizations++
|
numberOfOptimizations++
|
||||||
}
|
}
|
||||||
@ -59,7 +61,22 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
|||||||
return numberOfOptimizations
|
return numberOfOptimizations
|
||||||
}
|
}
|
||||||
|
|
||||||
fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
|
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
|
||||||
|
|
||||||
|
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
|
||||||
|
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
|
||||||
|
if(modification.remove)
|
||||||
|
lines.removeAt(modification.lineIndex)
|
||||||
|
else
|
||||||
|
lines[modification.lineIndex] = modification.replacement!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
||||||
|
// all lines (that aren't empty or comments) in sliding windows of certain size
|
||||||
|
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
||||||
|
|
||||||
|
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// the when statement (on bytes) generates a sequence of:
|
// the when statement (on bytes) generates a sequence of:
|
||||||
// lda $ce01,x
|
// lda $ce01,x
|
||||||
// cmp #$20
|
// cmp #$20
|
||||||
@ -68,42 +85,42 @@ fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int
|
|||||||
// cmp #$21
|
// cmp #$21
|
||||||
// beq check_prog8_s73choice_33
|
// beq check_prog8_s73choice_33
|
||||||
// the repeated lda can be removed
|
// the repeated lda can be removed
|
||||||
val removeLines = mutableListOf<Int>()
|
val mods = mutableListOf<Modification>()
|
||||||
for(lines in linesByFour) {
|
for(lines in linesByFour) {
|
||||||
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
|
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
|
||||||
lines[1].value.trim().startsWith("cmp ") &&
|
lines[1].value.trim().startsWith("cmp ") &&
|
||||||
lines[2].value.trim().startsWith("beq ") &&
|
lines[2].value.trim().startsWith("beq ") &&
|
||||||
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
|
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
|
||||||
removeLines.add(lines[3].index) // remove the second lda
|
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeLines
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
|
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
|
||||||
// this is a lot harder for word values because the instruction sequence varies.
|
// this is a lot harder for word values because the instruction sequence varies.
|
||||||
val removeLines = mutableListOf<Int>()
|
val mods = mutableListOf<Modification>()
|
||||||
for(lines in linesByFour) {
|
for(lines in linesByFour) {
|
||||||
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
|
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
|
||||||
lines[1].value.trim()=="dex" &&
|
lines[1].value.trim()=="dex" &&
|
||||||
lines[2].value.trim()=="inx" &&
|
lines[2].value.trim()=="inx" &&
|
||||||
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
|
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
|
||||||
removeLines.add(lines[1].index)
|
mods.add(Modification(lines[1].index, true, null))
|
||||||
removeLines.add(lines[2].index)
|
mods.add(Modification(lines[2].index, true, null))
|
||||||
removeLines.add(lines[3].index)
|
mods.add(Modification(lines[3].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeLines
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Int> {
|
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
|
||||||
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
|
||||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
// the float one is the one that requires 2*7=14 lines of code to check...
|
||||||
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
|
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
|
||||||
|
|
||||||
val removeLines = mutableListOf<Int>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (pair in linesByFourteen) {
|
for (pair in linesByFourteen) {
|
||||||
val first = pair[0].value.trimStart()
|
val first = pair[0].value.trimStart()
|
||||||
val second = pair[1].value.trimStart()
|
val second = pair[1].value.trimStart()
|
||||||
@ -122,8 +139,8 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
|||||||
val fourthvalue = sixth.substring(4)
|
val fourthvalue = sixth.substring(4)
|
||||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||||
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
|
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||||
removeLines.add(pair[4].index)
|
mods.add(Modification(pair[4].index, true, null))
|
||||||
removeLines.add(pair[5].index)
|
mods.add(Modification(pair[5].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +149,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
|||||||
val secondvalue = third.substring(4)
|
val secondvalue = third.substring(4)
|
||||||
if(firstvalue==secondvalue) {
|
if(firstvalue==secondvalue) {
|
||||||
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
|
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
|
||||||
removeLines.add(pair[2].index)
|
mods.add(Modification(pair[2].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,24 +168,20 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
|||||||
|
|
||||||
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
||||||
// identical float init
|
// identical float init
|
||||||
removeLines.add(pair[7].index)
|
mods.add(Modification(pair[7].index, true, null))
|
||||||
removeLines.add(pair[8].index)
|
mods.add(Modification(pair[8].index, true, null))
|
||||||
removeLines.add(pair[9].index)
|
mods.add(Modification(pair[9].index, true, null))
|
||||||
removeLines.add(pair[10].index)
|
mods.add(Modification(pair[10].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeLines
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
|
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// all lines (that aren't empty or comments) in sliding windows of certain size
|
|
||||||
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
|
|
||||||
|
|
||||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
|
|
||||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
|
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
|
||||||
val removeLines = mutableListOf<Int>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (pair in linesByFour) {
|
for (pair in linesByFour) {
|
||||||
val first = pair[0].value.trimStart()
|
val first = pair[0].value.trimStart()
|
||||||
val second = pair[1].value.trimStart()
|
val second = pair[1].value.trimStart()
|
||||||
@ -186,26 +199,40 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
|
|||||||
val firstLoc = first.substring(4)
|
val firstLoc = first.substring(4)
|
||||||
val secondLoc = second.substring(4)
|
val secondLoc = second.substring(4)
|
||||||
if (firstLoc == secondLoc) {
|
if (firstLoc == secondLoc) {
|
||||||
removeLines.add(pair[1].index)
|
mods.add(Modification(pair[1].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeLines
|
return mods
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun optimizeIncDec(linesByTwo: List<List<IndexedValue<String>>>): List<Int> {
|
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
|
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
|
||||||
val removeLines = mutableListOf<Int>()
|
val mods = mutableListOf<Modification>()
|
||||||
for (pair in linesByTwo) {
|
for (pair in linesByFour) {
|
||||||
val first = pair[0].value
|
val first = pair[0].value
|
||||||
val second = pair[1].value
|
val second = pair[1].value
|
||||||
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|
||||||
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|
||||||
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|
||||||
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
|
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
|
||||||
removeLines.add(pair[0].index)
|
mods.add(Modification(pair[0].index, true, null))
|
||||||
removeLines.add(pair[1].index)
|
mods.add(Modification(pair[1].index, true, null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return removeLines
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||||
|
// jsr Sub + rts -> jmp Sub
|
||||||
|
val mods = mutableListOf<Modification>()
|
||||||
|
for (pair in linesByFour) {
|
||||||
|
val first = pair[0].value
|
||||||
|
val second = pair[1].value
|
||||||
|
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
|
||||||
|
mods += Modification(pair[0].index, false, pair[0].value.replace("jsr", "jmp"))
|
||||||
|
mods += Modification(pair[1].index, true, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mods
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,33 +2,29 @@ package prog8.compiler.target.c64.codegen
|
|||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
import prog8.ast.IFunctionCall
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.ByteDatatypes
|
import prog8.ast.base.*
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.ast.base.Register
|
|
||||||
import prog8.ast.base.WordDatatypes
|
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.statements.AssignTarget
|
|
||||||
import prog8.ast.statements.FunctionCallStatement
|
import prog8.ast.statements.FunctionCallStatement
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||||
import prog8.compiler.toHex
|
import prog8.compiler.toHex
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.functions.FSignature
|
||||||
import prog8.functions.FunctionSignature
|
|
||||||
|
|
||||||
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
|
|
||||||
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) {
|
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature) {
|
||||||
translateFunctioncall(fcall, func, false)
|
translateFunctioncall(fcall, func, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FunctionSignature) {
|
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
|
||||||
translateFunctioncall(fcall, func, true)
|
translateFunctioncall(fcall, func, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) {
|
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean) {
|
||||||
val functionName = fcall.target.nameInSource.last()
|
val functionName = fcall.target.nameInSource.last()
|
||||||
if (discardResult) {
|
if (discardResult) {
|
||||||
if (func.pure)
|
if (func.pure)
|
||||||
@ -38,109 +34,184 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
}
|
}
|
||||||
|
|
||||||
when (functionName) {
|
when (functionName) {
|
||||||
"msb" -> {
|
"msb" -> funcMsb(fcall)
|
||||||
val arg = fcall.args.single()
|
"mkword" -> funcMkword(fcall, func)
|
||||||
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
"abs" -> funcAbs(fcall, func)
|
||||||
throw AssemblyError("msb required word argument")
|
"swap" -> funcSwap(fcall)
|
||||||
if (arg is NumericLiteralValue)
|
"strlen" -> funcStrlen(fcall)
|
||||||
throw AssemblyError("should have been const-folded")
|
"min", "max", "sum" -> funcMinMaxSum(fcall, functionName)
|
||||||
if (arg is IdentifierReference) {
|
"any", "all" -> funcAnyAll(fcall, functionName)
|
||||||
val sourceName = asmgen.asmIdentifierName(arg)
|
"sgn" -> funcSgn(fcall, func)
|
||||||
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
|
|
||||||
} else {
|
|
||||||
asmgen.translateExpression(arg)
|
|
||||||
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"mkword" -> {
|
|
||||||
translateFunctionArguments(fcall.args, func)
|
|
||||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x")
|
|
||||||
}
|
|
||||||
"abs" -> {
|
|
||||||
translateFunctionArguments(fcall.args, func)
|
|
||||||
val dt = fcall.args.single().inferType(program)
|
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
|
|
||||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"swap" -> {
|
|
||||||
val first = fcall.args[0]
|
|
||||||
val second = fcall.args[1]
|
|
||||||
asmgen.translateExpression(first)
|
|
||||||
asmgen.translateExpression(second)
|
|
||||||
// pop in reverse order
|
|
||||||
val firstTarget = AssignTarget.fromExpr(first)
|
|
||||||
val secondTarget = AssignTarget.fromExpr(second)
|
|
||||||
asmgen.assignFromEvalResult(firstTarget)
|
|
||||||
asmgen.assignFromEvalResult(secondTarget)
|
|
||||||
}
|
|
||||||
"strlen" -> {
|
|
||||||
outputPushAddressOfIdentifier(fcall.args[0])
|
|
||||||
asmgen.out(" jsr prog8_lib.func_strlen")
|
|
||||||
}
|
|
||||||
"min", "max", "sum" -> {
|
|
||||||
outputPushAddressAndLenghtOfArray(fcall.args[0])
|
|
||||||
val dt = fcall.args.single().inferType(program)
|
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
|
|
||||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
|
||||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
|
|
||||||
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
|
||||||
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
|
|
||||||
else -> throw AssemblyError("weird type $dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"any", "all" -> {
|
|
||||||
outputPushAddressAndLenghtOfArray(fcall.args[0])
|
|
||||||
val dt = fcall.args.single().inferType(program)
|
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
|
||||||
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
|
|
||||||
else -> throw AssemblyError("weird type $dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"sgn" -> {
|
|
||||||
translateFunctionArguments(fcall.args, func)
|
|
||||||
val dt = fcall.args.single().inferType(program)
|
|
||||||
when(dt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
DataType.UBYTE -> asmgen.out(" jsr math.sign_ub")
|
|
||||||
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
|
|
||||||
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
|
|
||||||
DataType.WORD -> asmgen.out(" jsr math.sign_w")
|
|
||||||
DataType.FLOAT -> asmgen.out(" jsr c64flt.sign_f")
|
|
||||||
else -> throw AssemblyError("weird type $dt")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"sin", "cos", "tan", "atan",
|
"sin", "cos", "tan", "atan",
|
||||||
"ln", "log2", "sqrt", "rad",
|
"ln", "log2", "sqrt", "rad",
|
||||||
"deg", "round", "floor", "ceil",
|
"deg", "round", "floor", "ceil",
|
||||||
"rdnf" -> {
|
"rdnf" -> funcVariousFloatFuncs(fcall, func, functionName)
|
||||||
translateFunctionArguments(fcall.args, func)
|
"lsl" -> funcLsl(fcall)
|
||||||
asmgen.out(" jsr c64flt.func_$functionName")
|
"lsr" -> funcLsr(fcall)
|
||||||
|
"rol" -> funcRol(fcall)
|
||||||
|
"rol2" -> funcRol2(fcall)
|
||||||
|
"ror" -> funcRor(fcall)
|
||||||
|
"ror2" -> funcRor2(fcall)
|
||||||
|
"sort" -> funcSort(fcall)
|
||||||
|
"reverse" -> funcReverse(fcall)
|
||||||
|
"rsave" -> {
|
||||||
|
// save cpu status flag and all registers A, X, Y.
|
||||||
|
// see http://6502.org/tutorials/register_preservation.html
|
||||||
|
asmgen.out(" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}")
|
||||||
}
|
}
|
||||||
"lsl" -> {
|
"rrestore" -> {
|
||||||
// in-place
|
// restore all registers and cpu status flag
|
||||||
|
asmgen.out(" pla | tay | pla | tax | pla | plp")
|
||||||
|
}
|
||||||
|
"clear_carry" -> asmgen.out(" clc")
|
||||||
|
"set_carry" -> asmgen.out(" sec")
|
||||||
|
"clear_irqd" -> asmgen.out(" cli")
|
||||||
|
"set_irqd" -> asmgen.out(" sei")
|
||||||
|
else -> {
|
||||||
|
translateFunctionArguments(fcall.args, func)
|
||||||
|
asmgen.out(" jsr prog8_lib.func_$functionName")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcReverse(fcall: IFunctionCall) {
|
||||||
|
val variable = fcall.args.single()
|
||||||
|
if (variable is IdentifierReference) {
|
||||||
|
val decl = variable.targetVarDecl(program.namespace)!!
|
||||||
|
val varName = asmgen.asmIdentifierName(variable)
|
||||||
|
val numElements = decl.arraysize!!.size()
|
||||||
|
when (decl.datatype) {
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1}
|
||||||
|
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||||
|
lda #$numElements
|
||||||
|
jsr prog8_lib.reverse_b
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1}
|
||||||
|
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||||
|
lda #$numElements
|
||||||
|
jsr prog8_lib.reverse_w
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1}
|
||||||
|
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||||
|
lda #$numElements
|
||||||
|
jsr prog8_lib.reverse_f
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcSort(fcall: IFunctionCall) {
|
||||||
|
val variable = fcall.args.single()
|
||||||
|
if (variable is IdentifierReference) {
|
||||||
|
val decl = variable.targetVarDecl(program.namespace)!!
|
||||||
|
val varName = asmgen.asmIdentifierName(variable)
|
||||||
|
val numElements = decl.arraysize!!.size()
|
||||||
|
when (decl.datatype) {
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1}
|
||||||
|
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||||
|
lda #$numElements
|
||||||
|
sta ${C64Zeropage.SCRATCH_B1}
|
||||||
|
""")
|
||||||
|
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$varName
|
||||||
|
ldy #>$varName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1}
|
||||||
|
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||||
|
lda #$numElements
|
||||||
|
sta ${C64Zeropage.SCRATCH_B1}
|
||||||
|
""")
|
||||||
|
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcRor2(fcall: IFunctionCall) {
|
||||||
val what = fcall.args.single()
|
val what = fcall.args.single()
|
||||||
val dt = what.inferType(program)
|
val dt = what.inferType(program)
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
in ByteDatatypes -> {
|
DataType.UBYTE -> {
|
||||||
when (what) {
|
when (what) {
|
||||||
is RegisterExpr -> {
|
is ArrayIndexedExpression -> {
|
||||||
when (what.register) {
|
asmgen.translateExpression(what.identifier)
|
||||||
Register.A -> asmgen.out(" asl a")
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
Register.X -> asmgen.out(" txa | asl a | tax")
|
asmgen.out(" jsr prog8_lib.ror2_array_ub")
|
||||||
Register.Y -> asmgen.out(" tya | asl a | tay")
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
|
|
||||||
is DirectMemoryRead -> {
|
is DirectMemoryRead -> {
|
||||||
if (what.addressExpression is NumericLiteralValue) {
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
val number = (what.addressExpression as NumericLiteralValue).number
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
asmgen.out(" asl ${number.toHex()}")
|
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror2_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcRor(fcall: IFunctionCall) {
|
||||||
|
val what = fcall.args.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.ror_array_ub")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" ror ${number.toHex()}")
|
||||||
} else {
|
} else {
|
||||||
asmgen.translateExpression(what.addressExpression)
|
asmgen.translateExpression(what.addressExpression)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -149,28 +220,27 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
sta (+) + 1
|
sta (+) + 1
|
||||||
lda $ESTACK_HI_HEX,x
|
lda $ESTACK_HI_HEX,x
|
||||||
sta (+) + 2
|
sta (+) + 2
|
||||||
+ asl 0 ; modified
|
+ ror ${'$'}ffff ; modified
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is ArrayIndexedExpression -> {
|
is IdentifierReference -> {
|
||||||
asmgen.translateExpression(what.identifier)
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
asmgen.out(" ror $variable")
|
||||||
asmgen.out(" jsr prog8_lib.lsl_array_b")
|
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in WordDatatypes -> {
|
DataType.UWORD -> {
|
||||||
when (what) {
|
when (what) {
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
asmgen.translateExpression(what.identifier)
|
asmgen.translateExpression(what.identifier)
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
asmgen.out(" jsr prog8_lib.lsl_array_w")
|
asmgen.out(" jsr prog8_lib.ror_array_uw")
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
asmgen.out(" asl $variable | rol $variable+1")
|
asmgen.out(" ror $variable+1 | ror $variable")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
@ -178,20 +248,110 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"lsr" -> {
|
|
||||||
// in-place
|
private fun funcRol2(fcall: IFunctionCall) {
|
||||||
val what = fcall.args.single()
|
val what = fcall.args.single()
|
||||||
val dt = what.inferType(program)
|
val dt = what.inferType(program)
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
when (what) {
|
when (what) {
|
||||||
is RegisterExpr -> {
|
is ArrayIndexedExpression -> {
|
||||||
when (what.register) {
|
asmgen.translateExpression(what.identifier)
|
||||||
Register.A -> asmgen.out(" lsr a")
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
Register.X -> asmgen.out(" txa | lsr a | tax")
|
asmgen.out(" jsr prog8_lib.rol2_array_ub")
|
||||||
Register.Y -> asmgen.out(" tya | lsr a | tay")
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol2_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcRol(fcall: IFunctionCall) {
|
||||||
|
val what = fcall.args.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol_array_ub")
|
||||||
|
}
|
||||||
|
is DirectMemoryRead -> {
|
||||||
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
|
asmgen.out(" rol ${number.toHex()}")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(what.addressExpression)
|
||||||
|
asmgen.out("""
|
||||||
|
inx
|
||||||
|
lda $ESTACK_LO_HEX,x
|
||||||
|
sta (+) + 1
|
||||||
|
lda $ESTACK_HI_HEX,x
|
||||||
|
sta (+) + 2
|
||||||
|
+ rol ${'$'}ffff ; modified
|
||||||
|
""")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" rol $variable")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
when (what) {
|
||||||
|
is ArrayIndexedExpression -> {
|
||||||
|
asmgen.translateExpression(what.identifier)
|
||||||
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
|
asmgen.out(" jsr prog8_lib.rol_array_uw")
|
||||||
|
}
|
||||||
|
is IdentifierReference -> {
|
||||||
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
|
asmgen.out(" rol $variable | rol $variable+1")
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcLsr(fcall: IFunctionCall) {
|
||||||
|
val what = fcall.args.single()
|
||||||
|
val dt = what.inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
when (what) {
|
||||||
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
|
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
|
||||||
is DirectMemoryRead -> {
|
is DirectMemoryRead -> {
|
||||||
if (what.addressExpression is NumericLiteralValue) {
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
@ -205,7 +365,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
sta (+) + 1
|
sta (+) + 1
|
||||||
lda $ESTACK_HI_HEX,x
|
lda $ESTACK_HI_HEX,x
|
||||||
sta (+) + 2
|
sta (+) + 2
|
||||||
+ lsr 0 ; modified
|
+ lsr ${'$'}ffff ; modified
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -262,22 +422,18 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"rol" -> {
|
|
||||||
// in-place
|
private fun funcLsl(fcall: IFunctionCall) {
|
||||||
val what = fcall.args.single()
|
val what = fcall.args.single()
|
||||||
val dt = what.inferType(program)
|
val dt = what.inferType(program)
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
DataType.UBYTE -> {
|
in ByteDatatypes -> {
|
||||||
when(what) {
|
when (what) {
|
||||||
is ArrayIndexedExpression -> {
|
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.rol_array_ub")
|
|
||||||
}
|
|
||||||
is DirectMemoryRead -> {
|
is DirectMemoryRead -> {
|
||||||
if (what.addressExpression is NumericLiteralValue) {
|
if (what.addressExpression is NumericLiteralValue) {
|
||||||
val number = (what.addressExpression as NumericLiteralValue).number
|
val number = (what.addressExpression as NumericLiteralValue).number
|
||||||
asmgen.out(" rol ${number.toHex()}")
|
asmgen.out(" asl ${number.toHex()}")
|
||||||
} else {
|
} else {
|
||||||
asmgen.translateExpression(what.addressExpression)
|
asmgen.translateExpression(what.addressExpression)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -286,86 +442,28 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
sta (+) + 1
|
sta (+) + 1
|
||||||
lda $ESTACK_HI_HEX,x
|
lda $ESTACK_HI_HEX,x
|
||||||
sta (+) + 2
|
sta (+) + 2
|
||||||
+ rol 0 ; modified
|
+ asl ${'$'}ffff ; modified
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is RegisterExpr -> {
|
|
||||||
when(what.register) {
|
|
||||||
Register.A -> asmgen.out(" rol a")
|
|
||||||
Register.X -> asmgen.out(" txa | rol a | tax")
|
|
||||||
Register.Y -> asmgen.out(" tya | rol a | tay")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" rol $variable")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
when(what) {
|
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
asmgen.translateExpression(what.identifier)
|
asmgen.translateExpression(what.identifier)
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
asmgen.out(" jsr prog8_lib.rol_array_uw")
|
asmgen.out(" jsr prog8_lib.lsl_array_b")
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" rol $variable | rol $variable+1")
|
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird type")
|
in WordDatatypes -> {
|
||||||
}
|
when (what) {
|
||||||
}
|
|
||||||
"rol2" -> {
|
|
||||||
// in-place
|
|
||||||
val what = fcall.args.single()
|
|
||||||
val dt = what.inferType(program)
|
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
when(what) {
|
|
||||||
is ArrayIndexedExpression -> {
|
is ArrayIndexedExpression -> {
|
||||||
asmgen.translateExpression(what.identifier)
|
asmgen.translateExpression(what.identifier)
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
asmgen.translateExpression(what.arrayspec.index)
|
||||||
asmgen.out(" jsr prog8_lib.rol2_array_ub")
|
asmgen.out(" jsr prog8_lib.lsl_array_w")
|
||||||
}
|
|
||||||
is DirectMemoryRead -> {
|
|
||||||
if (what.addressExpression is NumericLiteralValue) {
|
|
||||||
val number = (what.addressExpression as NumericLiteralValue).number
|
|
||||||
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
|
|
||||||
} else {
|
|
||||||
asmgen.translateExpression(what.addressExpression)
|
|
||||||
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is RegisterExpr -> {
|
|
||||||
when(what.register) {
|
|
||||||
Register.A -> asmgen.out(" cmp #\$80 | rol a ")
|
|
||||||
Register.X -> asmgen.out(" txa | cmp #\$80 | rol a | tax")
|
|
||||||
Register.Y -> asmgen.out(" tya | cmp #\$80 | rol a | tay")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
val variable = asmgen.asmIdentifierName(what)
|
||||||
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
|
asmgen.out(" asl $variable | rol $variable+1")
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
when(what) {
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.rol2_array_uw")
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
|
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
@ -373,197 +471,126 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"ror" -> {
|
|
||||||
// in-place
|
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) {
|
||||||
val what = fcall.args.single()
|
|
||||||
val dt = what.inferType(program)
|
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
when(what) {
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.ror_array_ub")
|
|
||||||
}
|
|
||||||
is DirectMemoryRead -> {
|
|
||||||
if (what.addressExpression is NumericLiteralValue) {
|
|
||||||
val number = (what.addressExpression as NumericLiteralValue).number
|
|
||||||
asmgen.out(" ror ${number.toHex()}")
|
|
||||||
} else {
|
|
||||||
asmgen.translateExpression(what.addressExpression)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda $ESTACK_LO_HEX,x
|
|
||||||
sta (+) + 1
|
|
||||||
lda $ESTACK_HI_HEX,x
|
|
||||||
sta (+) + 2
|
|
||||||
+ ror 0 ; modified
|
|
||||||
""") }
|
|
||||||
}
|
|
||||||
is RegisterExpr -> {
|
|
||||||
when(what.register) {
|
|
||||||
Register.A -> asmgen.out(" ror a")
|
|
||||||
Register.X -> asmgen.out(" txa | ror a | tax")
|
|
||||||
Register.Y -> asmgen.out(" tya | ror a | tay")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" ror $variable")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
when(what) {
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.ror_array_uw")
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" ror $variable+1 | ror $variable")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"ror2" -> {
|
|
||||||
// in-place
|
|
||||||
val what = fcall.args.single()
|
|
||||||
val dt = what.inferType(program)
|
|
||||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
when(what) {
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.ror2_array_ub")
|
|
||||||
}
|
|
||||||
is DirectMemoryRead -> {
|
|
||||||
if (what.addressExpression is NumericLiteralValue) {
|
|
||||||
val number = (what.addressExpression as NumericLiteralValue).number
|
|
||||||
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
|
|
||||||
} else {
|
|
||||||
asmgen.translateExpression(what.addressExpression)
|
|
||||||
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is RegisterExpr -> {
|
|
||||||
when(what.register) {
|
|
||||||
Register.A -> asmgen.out(" lsr a | bcc + | ora #\$80 |+ ")
|
|
||||||
Register.X -> asmgen.out(" txa | lsr a | bcc + | ora #\$80 |+ tax ")
|
|
||||||
Register.Y -> asmgen.out(" tya | lsr a | bcc + | ora #\$80 |+ tay ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
when(what) {
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
asmgen.translateExpression(what.identifier)
|
|
||||||
asmgen.translateExpression(what.arrayspec.index)
|
|
||||||
asmgen.out(" jsr prog8_lib.ror2_array_uw")
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val variable = asmgen.asmIdentifierName(what)
|
|
||||||
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"sort" -> {
|
|
||||||
val variable = fcall.args.single()
|
|
||||||
if(variable is IdentifierReference) {
|
|
||||||
val decl = variable.targetVarDecl(program.namespace)!!
|
|
||||||
val varName = asmgen.asmIdentifierName(variable)
|
|
||||||
val numElements = decl.arraysize!!.size()
|
|
||||||
when(decl.datatype) {
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$varName
|
|
||||||
ldy #>$varName
|
|
||||||
sta ${C64Zeropage.SCRATCH_W1}
|
|
||||||
sty ${C64Zeropage.SCRATCH_W1+1}
|
|
||||||
lda #$numElements
|
|
||||||
sta ${C64Zeropage.SCRATCH_B1}
|
|
||||||
""")
|
|
||||||
asmgen.out(if(decl.datatype==DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$varName
|
|
||||||
ldy #>$varName
|
|
||||||
sta ${C64Zeropage.SCRATCH_W1}
|
|
||||||
sty ${C64Zeropage.SCRATCH_W1+1}
|
|
||||||
lda #$numElements
|
|
||||||
sta ${C64Zeropage.SCRATCH_B1}
|
|
||||||
""")
|
|
||||||
asmgen.out(if(decl.datatype==DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> TODO("sort floats (consider another solution if possible - this will be very slow, if ever implemented)")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
"reverse" -> {
|
|
||||||
val variable = fcall.args.single()
|
|
||||||
if (variable is IdentifierReference) {
|
|
||||||
val decl = variable.targetVarDecl(program.namespace)!!
|
|
||||||
val varName = asmgen.asmIdentifierName(variable)
|
|
||||||
val numElements = decl.arraysize!!.size()
|
|
||||||
when (decl.datatype) {
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$varName
|
|
||||||
ldy #>$varName
|
|
||||||
sta ${C64Zeropage.SCRATCH_W1}
|
|
||||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
|
||||||
lda #$numElements
|
|
||||||
jsr prog8_lib.reverse_b
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
asmgen.out("""
|
|
||||||
lda #<$varName
|
|
||||||
ldy #>$varName
|
|
||||||
sta ${C64Zeropage.SCRATCH_W1}
|
|
||||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
|
||||||
lda #$numElements
|
|
||||||
jsr prog8_lib.reverse_w
|
|
||||||
""")
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> TODO("reverse floats (consider another solution if possible - this will be quite slow, if ever implemented)")
|
|
||||||
else -> throw AssemblyError("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"rsave" -> {
|
|
||||||
// save cpu status flag and all registers A, X, Y.
|
|
||||||
// see http://6502.org/tutorials/register_preservation.html
|
|
||||||
asmgen.out(" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}")
|
|
||||||
}
|
|
||||||
"rrestore" -> {
|
|
||||||
// restore all registers and cpu status flag
|
|
||||||
asmgen.out(" pla | tay | pla | tax | pla | plp")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
translateFunctionArguments(fcall.args, func)
|
translateFunctionArguments(fcall.args, func)
|
||||||
asmgen.out(" jsr prog8_lib.func_$functionName")
|
asmgen.out(" jsr c64flt.func_$functionName")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun funcSgn(fcall: IFunctionCall, func: FSignature) {
|
||||||
|
translateFunctionArguments(fcall.args, func)
|
||||||
|
val dt = fcall.args.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> asmgen.out(" jsr math.sign_ub")
|
||||||
|
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
|
||||||
|
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
|
||||||
|
DataType.WORD -> asmgen.out(" jsr math.sign_w")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.sign_f")
|
||||||
|
else -> throw AssemblyError("weird type $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcAnyAll(fcall: IFunctionCall, functionName: String) {
|
||||||
|
outputPushAddressAndLenghtOfArray(fcall.args[0])
|
||||||
|
val dt = fcall.args.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
||||||
|
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
||||||
|
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
|
||||||
|
else -> throw AssemblyError("weird type $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcMinMaxSum(fcall: IFunctionCall, functionName: String) {
|
||||||
|
outputPushAddressAndLenghtOfArray(fcall.args[0])
|
||||||
|
val dt = fcall.args.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
|
||||||
|
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
||||||
|
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
|
||||||
|
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
||||||
|
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
|
||||||
|
else -> throw AssemblyError("weird type $dt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcStrlen(fcall: IFunctionCall) {
|
||||||
|
outputPushAddressOfIdentifier(fcall.args[0])
|
||||||
|
asmgen.out(" jsr prog8_lib.func_strlen")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcSwap(fcall: IFunctionCall) {
|
||||||
|
val first = fcall.args[0]
|
||||||
|
val second = fcall.args[1]
|
||||||
|
if(first is IdentifierReference && second is IdentifierReference) {
|
||||||
|
val firstName = asmgen.asmIdentifierName(first)
|
||||||
|
val secondName = asmgen.asmIdentifierName(second)
|
||||||
|
val dt = first.inferType(program)
|
||||||
|
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
|
||||||
|
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | tya | sta $secondName")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
|
||||||
|
asmgen.out("""
|
||||||
|
ldy $firstName
|
||||||
|
lda $secondName
|
||||||
|
sta $firstName
|
||||||
|
sty $secondName
|
||||||
|
ldy $firstName+1
|
||||||
|
lda $secondName+1
|
||||||
|
sta $firstName+1
|
||||||
|
sty $secondName+1
|
||||||
|
""")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if(dt.istype(DataType.FLOAT)) {
|
||||||
|
asmgen.out("""
|
||||||
|
lda #<$firstName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1}
|
||||||
|
lda #>$firstName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W1+1}
|
||||||
|
lda #<$secondName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W2}
|
||||||
|
lda #>$secondName
|
||||||
|
sta ${C64Zeropage.SCRATCH_W2+1}
|
||||||
|
jsr c64flt.swap_floats
|
||||||
|
""")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// other types of swap() calls should have been replaced by a different statement sequence involving a temp variable
|
||||||
|
throw AssemblyError("no asm generation for swap funccall $fcall")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
|
||||||
|
translateFunctionArguments(fcall.args, func)
|
||||||
|
val dt = fcall.args.single().inferType(program)
|
||||||
|
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||||
|
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
|
||||||
|
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
|
||||||
|
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
|
||||||
|
else -> throw AssemblyError("weird type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcMkword(fcall: IFunctionCall, func: FSignature) {
|
||||||
|
translateFunctionArguments(fcall.args, func)
|
||||||
|
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun funcMsb(fcall: IFunctionCall) {
|
||||||
|
val arg = fcall.args.single()
|
||||||
|
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
||||||
|
throw AssemblyError("msb required word argument")
|
||||||
|
if (arg is NumericLiteralValue)
|
||||||
|
throw AssemblyError("should have been const-folded")
|
||||||
|
if (arg is IdentifierReference) {
|
||||||
|
val sourceName = asmgen.asmIdentifierName(arg)
|
||||||
|
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
|
||||||
|
} else {
|
||||||
|
asmgen.translateExpression(arg)
|
||||||
|
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -594,7 +621,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
|||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FunctionSignature) {
|
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FSignature) {
|
||||||
args.forEach {
|
args.forEach {
|
||||||
asmgen.translateExpression(it)
|
asmgen.translateExpression(it)
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,14 @@ package prog8.compiler.target.c64.codegen
|
|||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.compiler.toHex
|
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.target.c64.C64MachineDefinition
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS2_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS2_HEX
|
||||||
|
import prog8.compiler.toHex
|
||||||
import prog8.functions.BuiltinFunctions
|
import prog8.functions.BuiltinFunctions
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
@ -24,11 +25,9 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
|||||||
is AddressOf -> translateExpression(expression)
|
is AddressOf -> translateExpression(expression)
|
||||||
is DirectMemoryRead -> translateExpression(expression)
|
is DirectMemoryRead -> translateExpression(expression)
|
||||||
is NumericLiteralValue -> translateExpression(expression)
|
is NumericLiteralValue -> translateExpression(expression)
|
||||||
is RegisterExpr -> translateExpression(expression)
|
|
||||||
is IdentifierReference -> translateExpression(expression)
|
is IdentifierReference -> translateExpression(expression)
|
||||||
is FunctionCall -> translateExpression(expression)
|
is FunctionCall -> translateExpression(expression)
|
||||||
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
|
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||||
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
|
|
||||||
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,8 +38,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
|||||||
if (builtinFunc != null) {
|
if (builtinFunc != null) {
|
||||||
asmgen.translateFunctioncallExpression(expression, builtinFunc)
|
asmgen.translateFunctioncallExpression(expression, builtinFunc)
|
||||||
} else {
|
} else {
|
||||||
asmgen.translateFunctionCall(expression)
|
|
||||||
val sub = expression.target.targetSubroutine(program.namespace)!!
|
val sub = expression.target.targetSubroutine(program.namespace)!!
|
||||||
|
asmgen.translateFunctionCall(expression)
|
||||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||||
for ((_, reg) in returns) {
|
for ((_, reg) in returns) {
|
||||||
if (!reg.stack) {
|
if (!reg.stack) {
|
||||||
@ -50,7 +49,18 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
|||||||
RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
|
RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
|
||||||
RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
||||||
RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
|
RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
|
||||||
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable")
|
RegisterOrPair.X -> {
|
||||||
|
// return value in X register has been discarded, just push a zero
|
||||||
|
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | dex")
|
||||||
|
}
|
||||||
|
RegisterOrPair.AX -> {
|
||||||
|
// return value in X register has been discarded, just push a zero in this place
|
||||||
|
asmgen.out(" sta $ESTACK_LO_HEX,x | lda #0 | sta $ESTACK_HI_HEX,x | dex")
|
||||||
|
}
|
||||||
|
RegisterOrPair.XY -> {
|
||||||
|
// return value in X register has been discarded, just push a zero in this place
|
||||||
|
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
|
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
|
||||||
@ -109,7 +119,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
|||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else")
|
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,8 +136,16 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
|||||||
asmgen.out(" lda ${address.toHex()} | sta $ESTACK_LO_HEX,x | dex")
|
asmgen.out(" lda ${address.toHex()} | sta $ESTACK_LO_HEX,x | dex")
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
|
// the identifier is a pointer variable, so read the value from the address in it
|
||||||
val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference)
|
val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference)
|
||||||
asmgen.out(" lda $sourceName | sta $ESTACK_LO_HEX,x | dex")
|
asmgen.out("""
|
||||||
|
lda $sourceName
|
||||||
|
sta (+) +1
|
||||||
|
lda $sourceName+1
|
||||||
|
sta (+) +2
|
||||||
|
+ lda ${'$'}ffff ; modified
|
||||||
|
sta $ESTACK_LO_HEX,x
|
||||||
|
dex""")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
translateExpression(expr.addressExpression)
|
translateExpression(expr.addressExpression)
|
||||||
@ -155,14 +173,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateExpression(expr: RegisterExpr) {
|
|
||||||
when(expr.register) {
|
|
||||||
Register.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
|
|
||||||
Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register")
|
|
||||||
Register.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun translateExpression(expr: IdentifierReference) {
|
private fun translateExpression(expr: IdentifierReference) {
|
||||||
val varname = asmgen.asmIdentifierName(expr)
|
val varname = asmgen.asmIdentifierName(expr)
|
||||||
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
|
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||||
@ -201,10 +211,46 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
|||||||
translateExpression(expr.left)
|
translateExpression(expr.left)
|
||||||
val amount = expr.right.constValue(program)!!.number.toInt()
|
val amount = expr.right.constValue(program)!!.number.toInt()
|
||||||
when (leftDt) {
|
when (leftDt) {
|
||||||
DataType.UBYTE -> repeat(amount) { asmgen.out(" lsr $ESTACK_LO_PLUS1_HEX,x") }
|
DataType.UBYTE -> {
|
||||||
DataType.BYTE -> repeat(amount) { asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x") }
|
if(amount<=2)
|
||||||
DataType.UWORD -> repeat(amount) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
repeat(amount) { asmgen.out(" lsr $ESTACK_LO_PLUS1_HEX,x") }
|
||||||
DataType.WORD -> repeat(amount) { asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
else {
|
||||||
|
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x")
|
||||||
|
repeat(amount) { asmgen.out(" lsr a") }
|
||||||
|
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
if(amount<=2)
|
||||||
|
repeat(amount) { asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||||
|
else {
|
||||||
|
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | sta ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||||
|
repeat(amount) { asmgen.out(" asl a | ror ${C64MachineDefinition.C64Zeropage.SCRATCH_B1} | lda ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}") }
|
||||||
|
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
var left = amount
|
||||||
|
while(left>=7) {
|
||||||
|
asmgen.out(" jsr math.shift_right_uw_7")
|
||||||
|
left -= 7
|
||||||
|
}
|
||||||
|
if (left in 0..2)
|
||||||
|
repeat(left) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||||
|
else
|
||||||
|
asmgen.out(" jsr math.shift_right_uw_$left")
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
var left = amount
|
||||||
|
while(left>=7) {
|
||||||
|
asmgen.out(" jsr math.shift_right_w_7")
|
||||||
|
left -= 7
|
||||||
|
}
|
||||||
|
if (left in 0..2)
|
||||||
|
repeat(left) { asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||||
|
else
|
||||||
|
asmgen.out(" jsr math.shift_right_w_$left")
|
||||||
|
}
|
||||||
else -> throw AssemblyError("weird type")
|
else -> throw AssemblyError("weird type")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -213,10 +259,26 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
|||||||
// bit-shifts are always by a constant number (for now)
|
// bit-shifts are always by a constant number (for now)
|
||||||
translateExpression(expr.left)
|
translateExpression(expr.left)
|
||||||
val amount = expr.right.constValue(program)!!.number.toInt()
|
val amount = expr.right.constValue(program)!!.number.toInt()
|
||||||
if (leftDt in ByteDatatypes)
|
if (leftDt in ByteDatatypes) {
|
||||||
|
if(amount<=2)
|
||||||
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x") }
|
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x") }
|
||||||
|
else {
|
||||||
|
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x")
|
||||||
|
repeat(amount) { asmgen.out(" asl a") }
|
||||||
|
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var left=amount
|
||||||
|
while(left>=7) {
|
||||||
|
asmgen.out(" jsr math.shift_left_w_7")
|
||||||
|
left -= 7
|
||||||
|
}
|
||||||
|
if (left in 0..2)
|
||||||
|
repeat(left) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
|
||||||
else
|
else
|
||||||
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
|
asmgen.out(" jsr math.shift_left_w_$left")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
"*" -> {
|
"*" -> {
|
||||||
@ -224,8 +286,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
|||||||
if(value!=null) {
|
if(value!=null) {
|
||||||
if(rightDt in IntegerDatatypes) {
|
if(rightDt in IntegerDatatypes) {
|
||||||
val amount = value.number.toInt()
|
val amount = value.number.toInt()
|
||||||
if(amount in powersOfTwo)
|
|
||||||
printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount")
|
|
||||||
when(rightDt) {
|
when(rightDt) {
|
||||||
DataType.UBYTE -> {
|
DataType.UBYTE -> {
|
||||||
if(amount in optimizedByteMultiplications) {
|
if(amount in optimizedByteMultiplications) {
|
||||||
@ -275,8 +335,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
|||||||
// the general, non-optimized cases
|
// the general, non-optimized cases
|
||||||
translateExpression(expr.left)
|
translateExpression(expr.left)
|
||||||
translateExpression(expr.right)
|
translateExpression(expr.right)
|
||||||
if(leftDt!=rightDt)
|
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always?
|
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
||||||
|
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
||||||
|
|
||||||
when (leftDt) {
|
when (leftDt) {
|
||||||
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||||
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||||
|
@ -2,21 +2,20 @@ package prog8.compiler.target.c64.codegen
|
|||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.Register
|
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.RangeExpr
|
import prog8.ast.expressions.RangeExpr
|
||||||
import prog8.ast.statements.AssignTarget
|
import prog8.ast.statements.AssignTarget
|
||||||
import prog8.ast.statements.Assignment
|
import prog8.ast.statements.Assignment
|
||||||
import prog8.ast.statements.ForLoop
|
import prog8.ast.statements.ForLoop
|
||||||
|
import prog8.compiler.AssemblyError
|
||||||
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
|
||||||
import prog8.compiler.toHex
|
import prog8.compiler.toHex
|
||||||
import prog8.compiler.AssemblyError
|
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
// todo choose more efficient comparisons to avoid needless lda's
|
// todo choose more efficient comparisons to avoid needless lda's
|
||||||
// todo optimize common case step == 2 / -2
|
// todo optimize common case when step == 2 or -2
|
||||||
|
|
||||||
|
|
||||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
@ -37,7 +36,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
|||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
|
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("can't iterate over ${stmt.iterable}")
|
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,28 +54,8 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
|||||||
// bytes, step 1 or -1
|
// bytes, step 1 or -1
|
||||||
|
|
||||||
val incdec = if(stepsize==1) "inc" else "dec"
|
val incdec = if(stepsize==1) "inc" else "dec"
|
||||||
if (stmt.loopRegister != null) {
|
|
||||||
// loop register over range
|
|
||||||
if(stmt.loopRegister!= Register.A)
|
|
||||||
throw AssemblyError("can only use A")
|
|
||||||
asmgen.translateExpression(range.to)
|
|
||||||
asmgen.translateExpression(range.from)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${ESTACK_LO_HEX},x
|
|
||||||
sta $loopLabel+1
|
|
||||||
$loopLabel lda #0 ; modified""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel lda $loopLabel+1
|
|
||||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
beq $endLabel
|
|
||||||
$incdec $loopLabel+1
|
|
||||||
jmp $loopLabel
|
|
||||||
$endLabel inx""")
|
|
||||||
} else {
|
|
||||||
// loop over byte range via loopvar
|
// loop over byte range via loopvar
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||||
asmgen.translateExpression(range.to)
|
asmgen.translateExpression(range.to)
|
||||||
asmgen.translateExpression(range.from)
|
asmgen.translateExpression(range.from)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -93,46 +72,12 @@ $continueLabel lda $varname
|
|||||||
jmp $loopLabel
|
jmp $loopLabel
|
||||||
$endLabel inx""")
|
$endLabel inx""")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else {
|
else {
|
||||||
|
|
||||||
// bytes, step >= 2 or <= -2
|
// bytes, step >= 2 or <= -2
|
||||||
|
|
||||||
if (stmt.loopRegister != null) {
|
|
||||||
// loop register over range
|
|
||||||
if(stmt.loopRegister!= Register.A)
|
|
||||||
throw AssemblyError("can only use A")
|
|
||||||
asmgen.translateExpression(range.to)
|
|
||||||
asmgen.translateExpression(range.from)
|
|
||||||
asmgen.out("""
|
|
||||||
inx
|
|
||||||
lda ${ESTACK_LO_HEX},x
|
|
||||||
sta $loopLabel+1
|
|
||||||
$loopLabel lda #0 ; modified""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel lda $loopLabel+1""")
|
|
||||||
if(stepsize>0) {
|
|
||||||
asmgen.out("""
|
|
||||||
clc
|
|
||||||
adc #$stepsize
|
|
||||||
sta $loopLabel+1
|
|
||||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
bcc $loopLabel
|
|
||||||
beq $loopLabel""")
|
|
||||||
} else {
|
|
||||||
asmgen.out("""
|
|
||||||
sec
|
|
||||||
sbc #${stepsize.absoluteValue}
|
|
||||||
sta $loopLabel+1
|
|
||||||
cmp $ESTACK_LO_PLUS1_HEX,x
|
|
||||||
bcs $loopLabel""")
|
|
||||||
}
|
|
||||||
asmgen.out("""
|
|
||||||
$endLabel inx""")
|
|
||||||
} else {
|
|
||||||
// loop over byte range via loopvar
|
// loop over byte range via loopvar
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||||
asmgen.translateExpression(range.to)
|
asmgen.translateExpression(range.to)
|
||||||
asmgen.translateExpression(range.from)
|
asmgen.translateExpression(range.from)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -163,7 +108,6 @@ $continueLabel lda $varname""")
|
|||||||
$endLabel inx""")
|
$endLabel inx""")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
when {
|
when {
|
||||||
|
|
||||||
@ -171,8 +115,8 @@ $endLabel inx""")
|
|||||||
|
|
||||||
stepsize == 1 || stepsize == -1 -> {
|
stepsize == 1 || stepsize == -1 -> {
|
||||||
asmgen.translateExpression(range.to)
|
asmgen.translateExpression(range.to)
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||||
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
|
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
|
||||||
null, range.from, range.position)
|
null, range.from, range.position)
|
||||||
assignLoopvar.linkParents(stmt)
|
assignLoopvar.linkParents(stmt)
|
||||||
asmgen.translate(assignLoopvar)
|
asmgen.translate(assignLoopvar)
|
||||||
@ -207,8 +151,8 @@ $endLabel inx""")
|
|||||||
// (u)words, step >= 2
|
// (u)words, step >= 2
|
||||||
|
|
||||||
asmgen.translateExpression(range.to)
|
asmgen.translateExpression(range.to)
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||||
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
|
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
|
||||||
null, range.from, range.position)
|
null, range.from, range.position)
|
||||||
assignLoopvar.linkParents(stmt)
|
assignLoopvar.linkParents(stmt)
|
||||||
asmgen.translate(assignLoopvar)
|
asmgen.translate(assignLoopvar)
|
||||||
@ -256,8 +200,8 @@ $endLabel inx""")
|
|||||||
|
|
||||||
// (u)words, step <= -2
|
// (u)words, step <= -2
|
||||||
asmgen.translateExpression(range.to)
|
asmgen.translateExpression(range.to)
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||||
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
|
val assignLoopvar = Assignment(AssignTarget(stmt.loopVar, null, null, stmt.loopVar.position),
|
||||||
null, range.from, range.position)
|
null, range.from, range.position)
|
||||||
assignLoopvar.linkParents(stmt)
|
assignLoopvar.linkParents(stmt)
|
||||||
asmgen.translate(assignLoopvar)
|
asmgen.translate(assignLoopvar)
|
||||||
@ -319,8 +263,6 @@ $endLabel inx""")
|
|||||||
val decl = ident.targetVarDecl(program.namespace)!!
|
val decl = ident.targetVarDecl(program.namespace)!!
|
||||||
when(iterableDt) {
|
when(iterableDt) {
|
||||||
DataType.STR -> {
|
DataType.STR -> {
|
||||||
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
|
|
||||||
throw AssemblyError("can only use A")
|
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #<$iterableName
|
lda #<$iterableName
|
||||||
ldy #>$iterableName
|
ldy #>$iterableName
|
||||||
@ -328,8 +270,7 @@ $endLabel inx""")
|
|||||||
sty $loopLabel+2
|
sty $loopLabel+2
|
||||||
$loopLabel lda ${65535.toHex()} ; modified
|
$loopLabel lda ${65535.toHex()} ; modified
|
||||||
beq $endLabel""")
|
beq $endLabel""")
|
||||||
if(stmt.loopVar!=null)
|
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar)}")
|
||||||
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
|
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.body)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
$continueLabel inc $loopLabel+1
|
$continueLabel inc $loopLabel+1
|
||||||
@ -339,10 +280,8 @@ $continueLabel inc $loopLabel+1
|
|||||||
$endLabel""")
|
$endLabel""")
|
||||||
}
|
}
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||||
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
|
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases
|
||||||
val length = decl.arraysize!!.size()!!
|
val length = decl.arraysize!!.size()!!
|
||||||
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
|
|
||||||
throw AssemblyError("can only use A")
|
|
||||||
val counterLabel = asmgen.makeLabel("for_counter")
|
val counterLabel = asmgen.makeLabel("for_counter")
|
||||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
@ -353,8 +292,7 @@ $endLabel""")
|
|||||||
ldy #0
|
ldy #0
|
||||||
$loopLabel sty $counterLabel
|
$loopLabel sty $counterLabel
|
||||||
$modifiedLabel lda ${65535.toHex()},y ; modified""")
|
$modifiedLabel lda ${65535.toHex()},y ; modified""")
|
||||||
if(stmt.loopVar!=null)
|
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar)}")
|
||||||
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
|
|
||||||
asmgen.translate(stmt.body)
|
asmgen.translate(stmt.body)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
$continueLabel ldy $counterLabel
|
$continueLabel ldy $counterLabel
|
||||||
@ -366,14 +304,12 @@ $counterLabel .byte 0
|
|||||||
$endLabel""")
|
$endLabel""")
|
||||||
}
|
}
|
||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
|
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter var in such cases
|
||||||
val length = decl.arraysize!!.size()!! * 2
|
val length = decl.arraysize!!.size()!! * 2
|
||||||
if(stmt.loopRegister!=null)
|
|
||||||
throw AssemblyError("can't use register to loop over words")
|
|
||||||
val counterLabel = asmgen.makeLabel("for_counter")
|
val counterLabel = asmgen.makeLabel("for_counter")
|
||||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||||
val modifiedLabel2 = asmgen.makeLabel("for_modified2")
|
val modifiedLabel2 = asmgen.makeLabel("for_modified2")
|
||||||
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar!!)
|
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
lda #<$iterableName
|
lda #<$iterableName
|
||||||
ldy #>$iterableName
|
ldy #>$iterableName
|
||||||
@ -410,7 +346,7 @@ $endLabel""")
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
|
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
|
||||||
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter in such cases
|
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter var in such cases
|
||||||
if (range.isEmpty())
|
if (range.isEmpty())
|
||||||
throw AssemblyError("empty range")
|
throw AssemblyError("empty range")
|
||||||
val loopLabel = asmgen.makeLabel("for_loop")
|
val loopLabel = asmgen.makeLabel("for_loop")
|
||||||
@ -421,89 +357,8 @@ $endLabel""")
|
|||||||
when(iterableDt) {
|
when(iterableDt) {
|
||||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||||
val counterLabel = asmgen.makeLabel("for_counter")
|
val counterLabel = asmgen.makeLabel("for_counter")
|
||||||
if(stmt.loopRegister!=null) {
|
|
||||||
|
|
||||||
// loop register over range
|
|
||||||
|
|
||||||
if(stmt.loopRegister!= Register.A)
|
|
||||||
throw AssemblyError("can only use A")
|
|
||||||
when {
|
|
||||||
range.step==1 -> {
|
|
||||||
// step = 1
|
|
||||||
asmgen.out("""
|
|
||||||
lda #${range.first}
|
|
||||||
sta $loopLabel+1
|
|
||||||
lda #${range.last-range.first+1 and 255}
|
|
||||||
sta $counterLabel
|
|
||||||
$loopLabel lda #0 ; modified""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel dec $counterLabel
|
|
||||||
beq $endLabel
|
|
||||||
inc $loopLabel+1
|
|
||||||
jmp $loopLabel
|
|
||||||
$counterLabel .byte 0
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
range.step==-1 -> {
|
|
||||||
// step = -1
|
|
||||||
asmgen.out("""
|
|
||||||
lda #${range.first}
|
|
||||||
sta $loopLabel+1
|
|
||||||
lda #${range.first-range.last+1 and 255}
|
|
||||||
sta $counterLabel
|
|
||||||
$loopLabel lda #0 ; modified """)
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel dec $counterLabel
|
|
||||||
beq $endLabel
|
|
||||||
dec $loopLabel+1
|
|
||||||
jmp $loopLabel
|
|
||||||
$counterLabel .byte 0
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
range.step >= 2 -> {
|
|
||||||
// step >= 2
|
|
||||||
asmgen.out("""
|
|
||||||
lda #${(range.last-range.first) / range.step + 1}
|
|
||||||
sta $counterLabel
|
|
||||||
lda #${range.first}
|
|
||||||
$loopLabel pha""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel pla
|
|
||||||
dec $counterLabel
|
|
||||||
beq $endLabel
|
|
||||||
clc
|
|
||||||
adc #${range.step}
|
|
||||||
jmp $loopLabel
|
|
||||||
$counterLabel .byte 0
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// step <= -2
|
|
||||||
asmgen.out("""
|
|
||||||
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
|
|
||||||
sta $counterLabel
|
|
||||||
lda #${range.first}
|
|
||||||
$loopLabel pha""")
|
|
||||||
asmgen.translate(stmt.body)
|
|
||||||
asmgen.out("""
|
|
||||||
$continueLabel pla
|
|
||||||
dec $counterLabel
|
|
||||||
beq $endLabel
|
|
||||||
sec
|
|
||||||
sbc #${range.step.absoluteValue}
|
|
||||||
jmp $loopLabel
|
|
||||||
$counterLabel .byte 0
|
|
||||||
$endLabel""")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// loop over byte range via loopvar
|
// loop over byte range via loopvar
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||||
when {
|
when {
|
||||||
range.step==1 -> {
|
range.step==1 -> {
|
||||||
// step = 1
|
// step = 1
|
||||||
@ -581,10 +436,9 @@ $endLabel""")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||||
// loop over word range via loopvar
|
// loop over word range via loopvar
|
||||||
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
|
val varname = asmgen.asmIdentifierName(stmt.loopVar)
|
||||||
when {
|
when {
|
||||||
range.step == 1 -> {
|
range.step == 1 -> {
|
||||||
// word, step = 1
|
// word, step = 1
|
||||||
|
@ -7,10 +7,10 @@ import prog8.ast.expressions.*
|
|||||||
import prog8.ast.statements.AssignTarget
|
import prog8.ast.statements.AssignTarget
|
||||||
import prog8.ast.statements.Subroutine
|
import prog8.ast.statements.Subroutine
|
||||||
import prog8.ast.statements.SubroutineParameter
|
import prog8.ast.statements.SubroutineParameter
|
||||||
import prog8.compiler.toHex
|
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.AssemblyError
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
|
||||||
|
|
||||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
@ -19,33 +19,94 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
// output the code to setup the parameters and perform the actual call
|
// output the code to setup the parameters and perform the actual call
|
||||||
// does NOT output the code to deal with the result values!
|
// does NOT output the code to deal with the result values!
|
||||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||||
if(Register.X in sub.asmClobbers)
|
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult()
|
||||||
|
if(saveX)
|
||||||
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
||||||
|
|
||||||
val subName = asmgen.asmIdentifierName(stmt.target)
|
val subName = asmgen.asmIdentifierName(stmt.target)
|
||||||
if(stmt.args.isNotEmpty()) {
|
if(stmt.args.isNotEmpty()) {
|
||||||
|
if(sub.asmParameterRegisters.isEmpty()) {
|
||||||
|
// via variables
|
||||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||||
translateFuncArguments(arg.first, arg.second, sub)
|
argumentViaVariable(sub, arg.first, arg.second)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// via registers
|
||||||
|
if(sub.parameters.size==1) {
|
||||||
|
// just a single parameter, no risk of clobbering registers
|
||||||
|
argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0])
|
||||||
|
} else {
|
||||||
|
// multiple register arguments, risk of register clobbering.
|
||||||
|
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
|
||||||
|
when {
|
||||||
|
stmt.args.all {it is AddressOf ||
|
||||||
|
it is NumericLiteralValue ||
|
||||||
|
it is StringLiteralValue ||
|
||||||
|
it is ArrayLiteralValue ||
|
||||||
|
it is IdentifierReference} -> {
|
||||||
|
// no risk of clobbering for these simple argument types. Optimize the register loading.
|
||||||
|
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||||
|
argumentViaRegister(sub, arg.first, arg.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// Risk of clobbering due to complex expression args. Work via the stack.
|
||||||
|
argsViaStackEvaluation(stmt, sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
asmgen.out(" jsr $subName")
|
asmgen.out(" jsr $subName")
|
||||||
|
|
||||||
if(Register.X in sub.asmClobbers)
|
if(saveX)
|
||||||
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
|
private fun argsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||||
val sourceIDt = value.inferType(program)
|
for (arg in stmt.args.reversed())
|
||||||
if(!sourceIDt.isKnown)
|
asmgen.translateExpression(arg)
|
||||||
|
for (regparam in sub.asmParameterRegisters) {
|
||||||
|
when (regparam.registerOrPair) {
|
||||||
|
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
||||||
|
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
|
||||||
|
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
|
||||||
|
RegisterOrPair.AX -> throw AssemblyError("can't pop into X register - use a variable instead")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
|
||||||
|
RegisterOrPair.XY -> throw AssemblyError("can't pop into X register - use a variable instead")
|
||||||
|
null -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (regparam.statusflag) {
|
||||||
|
Statusflag.Pc -> asmgen.out("""
|
||||||
|
inx
|
||||||
|
pha
|
||||||
|
lda $ESTACK_LO_HEX,x
|
||||||
|
beq +
|
||||||
|
sec
|
||||||
|
bcs ++
|
||||||
|
+ clc
|
||||||
|
+ pla
|
||||||
|
""")
|
||||||
|
null -> {
|
||||||
|
}
|
||||||
|
else -> throw AssemblyError("can only use Carry as status flag parameter")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||||
|
// pass parameter via a regular variable (not via registers)
|
||||||
|
val valueIDt = value.inferType(program)
|
||||||
|
if(!valueIDt.isKnown)
|
||||||
throw AssemblyError("arg type unknown")
|
throw AssemblyError("arg type unknown")
|
||||||
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
|
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||||
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
|
if(!argumentTypeCompatible(valueDt, parameter.value.type))
|
||||||
throw AssemblyError("argument type incompatible")
|
throw AssemblyError("argument type incompatible")
|
||||||
if(sub.asmParameterRegisters.isEmpty()) {
|
|
||||||
// pass parameter via a variable
|
|
||||||
val paramVar = parameter.value
|
val paramVar = parameter.value
|
||||||
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
||||||
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
val target = AssignTarget(IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
||||||
target.linkParents(value.parent)
|
target.linkParents(value.parent)
|
||||||
when (value) {
|
when (value) {
|
||||||
is NumericLiteralValue -> {
|
is NumericLiteralValue -> {
|
||||||
@ -54,7 +115,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
|
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||||
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
|
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
|
||||||
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
|
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
|
||||||
else -> throw AssemblyError("weird parameter datatype")
|
else -> throw AssemblyError("weird parameter datatype")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,13 +125,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
|
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
|
||||||
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
|
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
|
||||||
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
|
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
|
||||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
|
||||||
else -> throw AssemblyError("weird parameter datatype")
|
else -> throw AssemblyError("weird parameter datatype")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is RegisterExpr -> {
|
|
||||||
asmgen.assignFromRegister(target, value.register)
|
|
||||||
}
|
|
||||||
is DirectMemoryRead -> {
|
is DirectMemoryRead -> {
|
||||||
when(value.addressExpression) {
|
when(value.addressExpression) {
|
||||||
is NumericLiteralValue -> {
|
is NumericLiteralValue -> {
|
||||||
@ -83,7 +141,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
else -> {
|
else -> {
|
||||||
asmgen.translateExpression(value.addressExpression)
|
asmgen.translateExpression(value.addressExpression)
|
||||||
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
|
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
|
||||||
asmgen.assignFromRegister(target, Register.A)
|
asmgen.assignFromRegister(target, CpuRegister.A)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,8 +150,17 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
asmgen.assignFromEvalResult(target)
|
asmgen.assignFromEvalResult(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// pass parameter via a register parameter
|
|
||||||
|
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||||
|
// pass argument via a register parameter
|
||||||
|
val valueIDt = value.inferType(program)
|
||||||
|
if(!valueIDt.isKnown)
|
||||||
|
throw AssemblyError("arg type unknown")
|
||||||
|
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
if(!argumentTypeCompatible(valueDt, parameter.value.type))
|
||||||
|
throw AssemblyError("argument type incompatible")
|
||||||
|
|
||||||
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
||||||
val statusflag = paramRegister.statusflag
|
val statusflag = paramRegister.statusflag
|
||||||
val register = paramRegister.registerOrPair
|
val register = paramRegister.registerOrPair
|
||||||
@ -122,32 +189,19 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
bcs ++
|
bcs ++
|
||||||
+ clc
|
+ clc
|
||||||
+
|
+
|
||||||
""")
|
|
||||||
}
|
|
||||||
is RegisterExpr -> {
|
|
||||||
when(value.register) {
|
|
||||||
Register.A -> asmgen.out(" cmp #0")
|
|
||||||
Register.X -> asmgen.out(" txa")
|
|
||||||
Register.Y -> asmgen.out(" tya")
|
|
||||||
}
|
|
||||||
asmgen.out("""
|
|
||||||
beq +
|
|
||||||
sec
|
|
||||||
bcs ++
|
|
||||||
+ clc
|
|
||||||
+
|
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
asmgen.translateExpression(value)
|
asmgen.translateExpression(value)
|
||||||
asmgen.out("""
|
asmgen.out("""
|
||||||
inx
|
inx
|
||||||
|
pha
|
||||||
lda $ESTACK_LO_HEX,x
|
lda $ESTACK_LO_HEX,x
|
||||||
beq +
|
beq +
|
||||||
sec
|
sec
|
||||||
bcs ++
|
bcs ++
|
||||||
+ clc
|
+ clc
|
||||||
+
|
+ pla
|
||||||
""")
|
""")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,14 +211,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
register!=null && register.name.length==1 -> {
|
register!=null && register.name.length==1 -> {
|
||||||
when (value) {
|
when (value) {
|
||||||
is NumericLiteralValue -> {
|
is NumericLiteralValue -> {
|
||||||
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
asmgen.assignToRegister(CpuRegister.valueOf(register.name), value.number.toShort(), null)
|
||||||
target.linkParents(value.parent)
|
|
||||||
asmgen.assignFromByteConstant(target, value.number.toShort())
|
|
||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
asmgen.assignToRegister(CpuRegister.valueOf(register.name), null, value)
|
||||||
target.linkParents(value.parent)
|
|
||||||
asmgen.assignFromByteVariable(target, value)
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
asmgen.translateExpression(value)
|
asmgen.translateExpression(value)
|
||||||
@ -202,6 +252,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val sourceName = asmgen.asmIdentifierName(value)
|
val sourceName = asmgen.asmIdentifierName(value)
|
||||||
|
if(valueDt in PassByReferenceDatatypes) {
|
||||||
|
when (register) {
|
||||||
|
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
|
||||||
|
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||||
|
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
when (register) {
|
when (register) {
|
||||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||||
@ -209,6 +267,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
asmgen.translateExpression(value)
|
asmgen.translateExpression(value)
|
||||||
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
|
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
|
||||||
@ -220,11 +279,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||||
if(argType isAssignableTo paramType)
|
if(argType isAssignableTo paramType)
|
||||||
return true
|
return true
|
||||||
|
if(argType in ByteDatatypes && paramType in ByteDatatypes)
|
||||||
|
return true
|
||||||
|
if(argType in WordDatatypes && paramType in WordDatatypes)
|
||||||
|
return true
|
||||||
|
|
||||||
// we have a special rule for some types.
|
// we have a special rule for some types.
|
||||||
// strings are assignable to UWORD, for example, and vice versa
|
// strings are assignable to UWORD, for example, and vice versa
|
||||||
|
@ -4,11 +4,10 @@ import prog8.ast.Program
|
|||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.expressions.RegisterExpr
|
|
||||||
import prog8.ast.statements.PostIncrDecr
|
import prog8.ast.statements.PostIncrDecr
|
||||||
import prog8.compiler.toHex
|
|
||||||
import prog8.compiler.AssemblyError
|
import prog8.compiler.AssemblyError
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||||
|
import prog8.compiler.toHex
|
||||||
|
|
||||||
|
|
||||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||||
@ -17,28 +16,10 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
val targetIdent = stmt.target.identifier
|
val targetIdent = stmt.target.identifier
|
||||||
val targetMemory = stmt.target.memoryAddress
|
val targetMemory = stmt.target.memoryAddress
|
||||||
val targetArrayIdx = stmt.target.arrayindexed
|
val targetArrayIdx = stmt.target.arrayindexed
|
||||||
val targetRegister = stmt.target.register
|
|
||||||
when {
|
when {
|
||||||
targetRegister!=null -> {
|
|
||||||
when(targetRegister) {
|
|
||||||
Register.A -> {
|
|
||||||
if(incr)
|
|
||||||
asmgen.out(" clc | adc #1 ")
|
|
||||||
else
|
|
||||||
asmgen.out(" sec | sbc #1 ")
|
|
||||||
}
|
|
||||||
Register.X -> {
|
|
||||||
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
|
|
||||||
}
|
|
||||||
Register.Y -> {
|
|
||||||
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
targetIdent!=null -> {
|
targetIdent!=null -> {
|
||||||
val what = asmgen.asmIdentifierName(targetIdent)
|
val what = asmgen.asmIdentifierName(targetIdent)
|
||||||
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)
|
when (stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)) {
|
||||||
when (dt) {
|
|
||||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||||
in WordDatatypes -> {
|
in WordDatatypes -> {
|
||||||
if(incr)
|
if(incr)
|
||||||
@ -66,7 +47,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
}
|
}
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
val what = asmgen.asmIdentifierName(addressExpr)
|
val what = asmgen.asmIdentifierName(addressExpr)
|
||||||
asmgen.out(if(incr) " inc $what" else " dec $what")
|
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
|
||||||
|
if(incr)
|
||||||
|
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||||
|
else
|
||||||
|
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||||
}
|
}
|
||||||
else -> throw AssemblyError("weird target type $targetMemory")
|
else -> throw AssemblyError("weird target type $targetMemory")
|
||||||
}
|
}
|
||||||
@ -99,18 +84,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
|||||||
else -> throw AssemblyError("need numeric type")
|
else -> throw AssemblyError("need numeric type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is RegisterExpr -> {
|
|
||||||
// TODO optimize common cases
|
|
||||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
|
||||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
|
||||||
}
|
|
||||||
is IdentifierReference -> {
|
is IdentifierReference -> {
|
||||||
// TODO optimize common cases
|
|
||||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// TODO optimize common cases
|
|
||||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||||
}
|
}
|
||||||
|
@ -7,88 +7,100 @@ import prog8.compiler.CompilerException
|
|||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
|
|
||||||
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
|
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
|
||||||
|
|
||||||
|
|
||||||
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
|
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
|
||||||
|
|
||||||
|
|
||||||
class FunctionSignature(val pure: Boolean, // does it have side effects?
|
class FSignature(val pure: Boolean, // does it have side effects?
|
||||||
val parameters: List<BuiltinFunctionParam>,
|
val parameters: List<FParam>,
|
||||||
val returntype: DataType?,
|
val returntype: DataType?,
|
||||||
val constExpressionFunc: ConstExpressionCaller? = null)
|
val constExpressionFunc: ConstExpressionCaller? = null)
|
||||||
|
|
||||||
|
|
||||||
val BuiltinFunctions = mapOf(
|
val BuiltinFunctions = mapOf(
|
||||||
// this set of function have no return value and operate in-place:
|
// this set of function have no return value and operate in-place:
|
||||||
"rol" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
"rol" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
"ror" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
"rol2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
"ror2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||||
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
"lsl" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
|
||||||
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
|
"lsr" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
|
||||||
"sort" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
|
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
"reverse" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
|
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||||
// these few have a return value depending on the argument(s):
|
// these few have a return value depending on the argument(s):
|
||||||
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
"max" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
|
||||||
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
"min" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
|
||||||
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||||
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||||
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||||
// normal functions follow:
|
// normal functions follow:
|
||||||
"sgn" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||||
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||||
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
"sin8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
|
||||||
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
"sin8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
|
||||||
"sin16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
"sin16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
|
||||||
"sin16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
"sin16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
|
||||||
"cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
|
"cos" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
|
||||||
"cos8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
"cos8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
|
||||||
"cos8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
"cos8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
|
||||||
"cos16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
"cos16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
|
||||||
"cos16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
"cos16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
|
||||||
"tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
|
"tan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
|
||||||
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
"atan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||||
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
|
"ln" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
|
||||||
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
"log2" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
||||||
"sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
"sqrt16" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
|
||||||
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
"sqrt" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||||
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
"rad" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
|
||||||
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
"deg" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||||
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
"round" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
|
||||||
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
"floor" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||||
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
"ceil" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||||
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||||
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||||
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
|
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
|
||||||
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
|
"msb" to FSignature(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}},
|
||||||
"mkword" to FunctionSignature(true, listOf(
|
"mkword" to FSignature(true, listOf(FParam("lsb", setOf(DataType.UBYTE)), FParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||||
BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)),
|
"rnd" to FSignature(true, emptyList(), DataType.UBYTE),
|
||||||
BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
"rndw" to FSignature(true, emptyList(), DataType.UWORD),
|
||||||
"rnd" to FunctionSignature(true, emptyList(), DataType.UBYTE),
|
"rndf" to FSignature(true, emptyList(), DataType.FLOAT),
|
||||||
"rndw" to FunctionSignature(true, emptyList(), DataType.UWORD),
|
"exit" to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
|
||||||
"rndf" to FunctionSignature(true, emptyList(), DataType.FLOAT),
|
"rsave" to FSignature(false, emptyList(), null),
|
||||||
"rsave" to FunctionSignature(false, emptyList(), null),
|
"rrestore" to FSignature(false, emptyList(), null),
|
||||||
"rrestore" to FunctionSignature(false, emptyList(), null),
|
"set_carry" to FSignature(false, emptyList(), null),
|
||||||
"set_carry" to FunctionSignature(false, emptyList(), null),
|
"clear_carry" to FSignature(false, emptyList(), null),
|
||||||
"clear_carry" to FunctionSignature(false, emptyList(), null),
|
"set_irqd" to FSignature(false, emptyList(), null),
|
||||||
"set_irqd" to FunctionSignature(false, emptyList(), null),
|
"clear_irqd" to FSignature(false, emptyList(), null),
|
||||||
"clear_irqd" to FunctionSignature(false, emptyList(), null),
|
"read_flags" to FSignature(false, emptyList(), DataType.UBYTE),
|
||||||
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
|
"swap" to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
|
||||||
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
|
"memcopy" to FSignature(false, listOf(
|
||||||
"memcopy" to FunctionSignature(false, listOf(
|
FParam("from", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("from", IterableDatatypes + DataType.UWORD),
|
FParam("to", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("to", IterableDatatypes + DataType.UWORD),
|
FParam("numbytes", setOf(DataType.UBYTE))), null),
|
||||||
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
|
"memset" to FSignature(false, listOf(
|
||||||
"memset" to FunctionSignature(false, listOf(
|
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
|
FParam("numbytes", setOf(DataType.UWORD)),
|
||||||
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
|
FParam("bytevalue", ByteDatatypes)), null),
|
||||||
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
|
"memsetw" to FSignature(false, listOf(
|
||||||
"memsetw" to FunctionSignature(false, listOf(
|
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||||
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
|
FParam("numwords", setOf(DataType.UWORD)),
|
||||||
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
|
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||||
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
|
||||||
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen)
|
"substr" to FSignature(false, listOf(
|
||||||
|
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||||
|
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||||
|
FParam("start", setOf(DataType.UBYTE)),
|
||||||
|
FParam("length", setOf(DataType.UBYTE))), null),
|
||||||
|
"leftstr" to FSignature(false, listOf(
|
||||||
|
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||||
|
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||||
|
FParam("length", setOf(DataType.UBYTE))), null),
|
||||||
|
"rightstr" to FSignature(false, listOf(
|
||||||
|
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||||
|
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||||
|
FParam("length", setOf(DataType.UBYTE))), null)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
|
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
|
||||||
@ -173,6 +185,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
|||||||
|
|
||||||
|
|
||||||
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
||||||
|
class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
|
||||||
|
|
||||||
|
|
||||||
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
||||||
@ -253,17 +266,22 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
|||||||
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
|
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
|
||||||
if(args[0] !is IdentifierReference)
|
if(args[0] !is IdentifierReference)
|
||||||
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
||||||
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
|
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
|
||||||
|
?: throw CannotEvaluateException("len", "no target vardecl")
|
||||||
|
|
||||||
return when(target.datatype) {
|
return when(target.datatype) {
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
arraySize = target.arraysize!!.size()!!
|
arraySize = target.arraysize?.size()
|
||||||
|
if(arraySize==null)
|
||||||
|
throw CannotEvaluateException("len", "arraysize unknown")
|
||||||
if(arraySize>256)
|
if(arraySize>256)
|
||||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> {
|
DataType.ARRAY_F -> {
|
||||||
arraySize = target.arraysize!!.size()!!
|
arraySize = target.arraysize?.size()
|
||||||
|
if(arraySize==null)
|
||||||
|
throw CannotEvaluateException("len", "arraysize unknown")
|
||||||
if(arraySize>256)
|
if(arraySize>256)
|
||||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||||
|
157
compiler/src/prog8/optimizer/AssignmentTransformer.kt
Normal file
157
compiler/src/prog8/optimizer/AssignmentTransformer.kt
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
package prog8.optimizer
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.ErrorReporter
|
||||||
|
import prog8.ast.expressions.BinaryExpression
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstModification
|
||||||
|
import prog8.ast.statements.Assignment
|
||||||
|
import prog8.ast.statements.PostIncrDecr
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
internal class AssignmentTransformer(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||||
|
|
||||||
|
var optimizationsDone: Int = 0
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
// modify A = A + 5 back into augmented form A += 5 for easier code generation for optimized in-place assignments
|
||||||
|
// also to put code generation stuff together, single value assignment (A = 5) is converted to a special
|
||||||
|
// augmented form as wel (with the operator "setvalue")
|
||||||
|
if (assignment.aug_op == null) {
|
||||||
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
|
if (binExpr != null) {
|
||||||
|
if (assignment.target.isSameAs(binExpr.left)) {
|
||||||
|
assignment.value = binExpr.right
|
||||||
|
assignment.aug_op = binExpr.operator + "="
|
||||||
|
assignment.value.parent = assignment
|
||||||
|
optimizationsDone++
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assignment.aug_op = "setvalue"
|
||||||
|
optimizationsDone++
|
||||||
|
} else if(assignment.aug_op == "+=") {
|
||||||
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
|
if (binExpr != null) {
|
||||||
|
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
|
||||||
|
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
|
||||||
|
if(binExpr.operator == "+") {
|
||||||
|
when {
|
||||||
|
leftnum == 1.0 -> {
|
||||||
|
optimizationsDone++
|
||||||
|
return listOf(IAstModification.SwapOperands(binExpr))
|
||||||
|
}
|
||||||
|
leftnum == 2.0 -> {
|
||||||
|
optimizationsDone++
|
||||||
|
return listOf(IAstModification.SwapOperands(binExpr))
|
||||||
|
}
|
||||||
|
rightnum == 1.0 -> {
|
||||||
|
// x += y + 1 -> x += y , x++
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rightnum == 2.0 -> {
|
||||||
|
// x += y + 2 -> x += y , x++, x++
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(binExpr.operator == "-") {
|
||||||
|
when {
|
||||||
|
leftnum == 1.0 -> {
|
||||||
|
optimizationsDone++
|
||||||
|
return listOf(IAstModification.SwapOperands(binExpr))
|
||||||
|
}
|
||||||
|
leftnum == 2.0 -> {
|
||||||
|
optimizationsDone++
|
||||||
|
return listOf(IAstModification.SwapOperands(binExpr))
|
||||||
|
}
|
||||||
|
rightnum == 1.0 -> {
|
||||||
|
// x += y - 1 -> x += y , x--
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rightnum == 2.0 -> {
|
||||||
|
// x += y - 2 -> x += y , x--, x--
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(assignment.aug_op == "-=") {
|
||||||
|
val binExpr = assignment.value as? BinaryExpression
|
||||||
|
if (binExpr != null) {
|
||||||
|
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
|
||||||
|
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
|
||||||
|
if(binExpr.operator == "+") {
|
||||||
|
when {
|
||||||
|
leftnum == 1.0 -> {
|
||||||
|
optimizationsDone++
|
||||||
|
return listOf(IAstModification.SwapOperands(binExpr))
|
||||||
|
}
|
||||||
|
leftnum == 2.0 -> {
|
||||||
|
optimizationsDone++
|
||||||
|
return listOf(IAstModification.SwapOperands(binExpr))
|
||||||
|
}
|
||||||
|
rightnum == 1.0 -> {
|
||||||
|
// x -= y + 1 -> x -= y , x--
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rightnum == 2.0 -> {
|
||||||
|
// x -= y + 2 -> x -= y , x--, x--
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(binExpr.operator == "-") {
|
||||||
|
when {
|
||||||
|
leftnum == 1.0 -> {
|
||||||
|
optimizationsDone++
|
||||||
|
return listOf(IAstModification.SwapOperands(binExpr))
|
||||||
|
}
|
||||||
|
leftnum == 2.0 -> {
|
||||||
|
optimizationsDone++
|
||||||
|
return listOf(IAstModification.SwapOperands(binExpr))
|
||||||
|
}
|
||||||
|
rightnum == 1.0 -> {
|
||||||
|
// x -= y - 1 -> x -= y , x++
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rightnum == 2.0 -> {
|
||||||
|
// x -= y - 2 -> x -= y , x++, x++
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
|
||||||
|
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
}
|
@ -6,22 +6,30 @@ import prog8.ast.Node
|
|||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.ParentSentinel
|
import prog8.ast.base.ParentSentinel
|
||||||
import prog8.ast.base.VarDeclType
|
|
||||||
import prog8.ast.base.initvarsSubName
|
|
||||||
import prog8.ast.expressions.FunctionCall
|
import prog8.ast.expressions.FunctionCall
|
||||||
import prog8.ast.expressions.IdentifierReference
|
import prog8.ast.expressions.IdentifierReference
|
||||||
import prog8.ast.processing.IAstVisitor
|
import prog8.ast.processing.IAstVisitor
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.loadAsmIncludeFile
|
import prog8.compiler.loadAsmIncludeFile
|
||||||
|
|
||||||
|
private val alwaysKeepSubroutines = setOf(
|
||||||
|
Pair("main", "start"),
|
||||||
|
Pair("irq", "irq"),
|
||||||
|
Pair("prog8_lib", "init_system")
|
||||||
|
)
|
||||||
|
|
||||||
class CallGraph(private val program: Program): IAstVisitor {
|
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)
|
||||||
|
|
||||||
val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
|
||||||
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
class CallGraph(private val program: Program) : IAstVisitor {
|
||||||
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
|
||||||
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||||
// TODO add dataflow graph: what statements use what variables
|
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||||
|
val calls = mutableMapOf<INameScope, 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 usedSymbols = mutableSetOf<Statement>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -31,9 +39,9 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
|
fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
|
||||||
fun findSubs(scope: INameScope) {
|
fun findSubs(scope: INameScope) {
|
||||||
scope.statements.forEach {
|
scope.statements.forEach {
|
||||||
if(it is Subroutine)
|
if (it is Subroutine)
|
||||||
sub(it)
|
sub(it)
|
||||||
if(it is INameScope)
|
if (it is INameScope)
|
||||||
findSubs(it)
|
findSubs(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,17 +55,8 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
it.importedBy.clear()
|
it.importedBy.clear()
|
||||||
it.imports.clear()
|
it.imports.clear()
|
||||||
|
|
||||||
it.importedBy.addAll(modulesImportedBy.getValue(it))
|
it.importedBy.addAll(importedBy.getValue(it))
|
||||||
it.imports.addAll(modulesImporting.getValue(it))
|
it.imports.addAll(imports.getValue(it))
|
||||||
|
|
||||||
forAllSubroutines(it) { sub ->
|
|
||||||
sub.calledBy.clear()
|
|
||||||
sub.calls.clear()
|
|
||||||
|
|
||||||
sub.calledBy.addAll(subroutinesCalledBy.getValue(sub))
|
|
||||||
sub.calls.addAll(subroutinesCalling.getValue(sub))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val rootmodule = program.modules.first()
|
val rootmodule = program.modules.first()
|
||||||
@ -65,7 +64,7 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(block: Block) {
|
override fun visit(block: Block) {
|
||||||
if(block.definingModule().isLibraryModule) {
|
if (block.definingModule().isLibraryModule) {
|
||||||
// make sure the block is not removed
|
// make sure the block is not removed
|
||||||
addNodeAndParentScopes(block)
|
addNodeAndParentScopes(block)
|
||||||
}
|
}
|
||||||
@ -75,11 +74,11 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
|
|
||||||
override fun visit(directive: Directive) {
|
override fun visit(directive: Directive) {
|
||||||
val thisModule = directive.definingModule()
|
val thisModule = directive.definingModule()
|
||||||
if(directive.directive=="%import") {
|
if (directive.directive == "%import") {
|
||||||
val importedModule: Module = program.modules.single { it.name==directive.args[0].name }
|
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
||||||
modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule)
|
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
|
||||||
modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule)
|
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
|
||||||
} else if (directive.directive=="%asminclude") {
|
} else if (directive.directive == "%asminclude") {
|
||||||
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
|
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
|
||||||
val scope = directive.definingScope()
|
val scope = directive.definingScope()
|
||||||
scanAssemblyCode(asm, directive, scope)
|
scanAssemblyCode(asm, directive, scope)
|
||||||
@ -91,7 +90,7 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
override fun visit(identifier: IdentifierReference) {
|
override fun visit(identifier: IdentifierReference) {
|
||||||
// track symbol usage
|
// track symbol usage
|
||||||
val target = identifier.targetStatement(this.program.namespace)
|
val target = identifier.targetStatement(this.program.namespace)
|
||||||
if(target!=null) {
|
if (target != null) {
|
||||||
addNodeAndParentScopes(target)
|
addNodeAndParentScopes(target)
|
||||||
}
|
}
|
||||||
super.visit(identifier)
|
super.visit(identifier)
|
||||||
@ -99,24 +98,18 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
|
|
||||||
private fun addNodeAndParentScopes(stmt: Statement) {
|
private fun addNodeAndParentScopes(stmt: Statement) {
|
||||||
usedSymbols.add(stmt)
|
usedSymbols.add(stmt)
|
||||||
var node: Node=stmt
|
var node: Node = stmt
|
||||||
do {
|
do {
|
||||||
if(node is INameScope && node is Statement) {
|
if (node is INameScope && node is Statement) {
|
||||||
usedSymbols.add(node)
|
usedSymbols.add(node)
|
||||||
}
|
}
|
||||||
node=node.parent
|
node = node.parent
|
||||||
} while (node !is Module && node !is ParentSentinel)
|
} while (node !is Module && node !is ParentSentinel)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine) {
|
override fun visit(subroutine: Subroutine) {
|
||||||
val alwaysKeepSubroutines = setOf(
|
if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|
||||||
Pair("main", "start"),
|
|| subroutine.definingModule().isLibraryModule) {
|
||||||
Pair("irq", "irq"),
|
|
||||||
Pair("prog8_lib", "init_system")
|
|
||||||
)
|
|
||||||
|
|
||||||
if(Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|
|
||||||
|| subroutine.name== initvarsSubName || subroutine.definingModule().isLibraryModule) {
|
|
||||||
// make sure the entrypoint is mentioned in the used symbols
|
// make sure the entrypoint is mentioned in the used symbols
|
||||||
addNodeAndParentScopes(subroutine)
|
addNodeAndParentScopes(subroutine)
|
||||||
}
|
}
|
||||||
@ -124,12 +117,12 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl) {
|
override fun visit(decl: VarDecl) {
|
||||||
if(decl.autogeneratedDontRemove || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
|
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
|
||||||
// make sure autogenerated vardecls are in the used symbols
|
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
|
||||||
addNodeAndParentScopes(decl)
|
addNodeAndParentScopes(decl)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(decl.datatype==DataType.STRUCT)
|
if (decl.datatype == DataType.STRUCT)
|
||||||
addNodeAndParentScopes(decl)
|
addNodeAndParentScopes(decl)
|
||||||
|
|
||||||
super.visit(decl)
|
super.visit(decl)
|
||||||
@ -137,10 +130,10 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
|
|
||||||
override fun visit(functionCall: FunctionCall) {
|
override fun visit(functionCall: FunctionCall) {
|
||||||
val otherSub = functionCall.target.targetSubroutine(program.namespace)
|
val otherSub = functionCall.target.targetSubroutine(program.namespace)
|
||||||
if(otherSub!=null) {
|
if (otherSub != null) {
|
||||||
functionCall.definingSubroutine()?.let { thisSub ->
|
functionCall.definingSubroutine()?.let { thisSub ->
|
||||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCall)
|
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.visit(functionCall)
|
super.visit(functionCall)
|
||||||
@ -148,10 +141,10 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||||
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
|
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||||
if(otherSub!=null) {
|
if (otherSub != null) {
|
||||||
functionCallStatement.definingSubroutine()?.let { thisSub ->
|
functionCallStatement.definingSubroutine()?.let { thisSub ->
|
||||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCallStatement)
|
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.visit(functionCallStatement)
|
super.visit(functionCallStatement)
|
||||||
@ -159,10 +152,10 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
|
|
||||||
override fun visit(jump: Jump) {
|
override fun visit(jump: Jump) {
|
||||||
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
|
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
|
||||||
if(otherSub!=null) {
|
if (otherSub != null) {
|
||||||
jump.definingSubroutine()?.let { thisSub ->
|
jump.definingSubroutine()?.let { thisSub ->
|
||||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(jump)
|
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.visit(jump)
|
super.visit(jump)
|
||||||
@ -181,8 +174,6 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
|
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
|
||||||
val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
|
||||||
val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
|
||||||
asm.lines().forEach { line ->
|
asm.lines().forEach { line ->
|
||||||
val matches = asmJumpRx.matchEntire(line)
|
val matches = asmJumpRx.matchEntire(line)
|
||||||
if (matches != null) {
|
if (matches != null) {
|
||||||
@ -190,27 +181,27 @@ class CallGraph(private val program: Program): IAstVisitor {
|
|||||||
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
|
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
|
||||||
val node = program.namespace.lookup(jumptarget.split('.'), context)
|
val node = program.namespace.lookup(jumptarget.split('.'), context)
|
||||||
if (node is Subroutine) {
|
if (node is Subroutine) {
|
||||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
calls[scope] = calls.getValue(scope).plus(node)
|
||||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||||
} else if(jumptarget.contains('.')) {
|
} else if (jumptarget.contains('.')) {
|
||||||
// maybe only the first part already refers to a subroutine
|
// maybe only the first part already refers to a subroutine
|
||||||
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
|
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
|
||||||
if (node2 is Subroutine) {
|
if (node2 is Subroutine) {
|
||||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node2)
|
calls[scope] = calls.getValue(scope).plus(node2)
|
||||||
subroutinesCalledBy[node2] = subroutinesCalledBy.getValue(node2).plus(context)
|
calledBy[node2] = calledBy.getValue(node2).plus(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val matches2 = asmRefRx.matchEntire(line)
|
val matches2 = asmRefRx.matchEntire(line)
|
||||||
if (matches2 != null) {
|
if (matches2 != null) {
|
||||||
val target= matches2.groups[2]?.value
|
val target = matches2.groups[2]?.value
|
||||||
if (target != null && (target[0].isLetter() || target[0] == '_')) {
|
if (target != null && (target[0].isLetter() || target[0] == '_')) {
|
||||||
if(target.contains('.')) {
|
if (target.contains('.')) {
|
||||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||||
if (node is Subroutine) {
|
if (node is Subroutine) {
|
||||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
calls[scope] = calls.getValue(scope).plus(node)
|
||||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,712 +0,0 @@
|
|||||||
package prog8.optimizer
|
|
||||||
|
|
||||||
import prog8.ast.IFunctionCall
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
|
||||||
import prog8.ast.processing.fixupArrayEltDatatypesFromVardecl
|
|
||||||
import prog8.ast.processing.fixupArrayEltDatatypes
|
|
||||||
import prog8.ast.statements.*
|
|
||||||
import prog8.compiler.target.CompilationTarget
|
|
||||||
import prog8.functions.BuiltinFunctions
|
|
||||||
import kotlin.math.floor
|
|
||||||
|
|
||||||
|
|
||||||
class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
|
|
||||||
var optimizationsDone: Int = 0
|
|
||||||
var errors : MutableList<AstException> = mutableListOf()
|
|
||||||
private val reportedErrorMessages = mutableSetOf<String>()
|
|
||||||
|
|
||||||
fun addError(x: AstException) {
|
|
||||||
// check that we don't add the isSameAs error more than once
|
|
||||||
if(x.toString() !in reportedErrorMessages) {
|
|
||||||
reportedErrorMessages.add(x.toString())
|
|
||||||
errors.add(x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): Statement {
|
|
||||||
// the initializer value can't refer to the variable itself (recursive definition)
|
|
||||||
// TODO: use call tree for this?
|
|
||||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
|
||||||
errors.add(ExpressionError("recursive var declaration", decl.position))
|
|
||||||
return decl
|
|
||||||
}
|
|
||||||
|
|
||||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
|
||||||
if(decl.isArray){
|
|
||||||
if(decl.arraysize==null) {
|
|
||||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
|
||||||
val arrayval = decl.value as? ArrayLiteralValue
|
|
||||||
if(arrayval!=null) {
|
|
||||||
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position)
|
|
||||||
optimizationsDone++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(decl.arraysize?.size()==null) {
|
|
||||||
val size = decl.arraysize!!.index.accept(this)
|
|
||||||
if(size is NumericLiteralValue) {
|
|
||||||
decl.arraysize = ArrayIndex(size, decl.position)
|
|
||||||
optimizationsDone++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when(decl.datatype) {
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
|
||||||
val litval = decl.value as? NumericLiteralValue
|
|
||||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
|
||||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
|
||||||
decl.value = newValue
|
|
||||||
optimizationsDone++
|
|
||||||
return super.visit(decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
|
||||||
val numericLv = decl.value as? NumericLiteralValue
|
|
||||||
val rangeExpr = decl.value as? RangeExpr
|
|
||||||
if(rangeExpr!=null) {
|
|
||||||
// convert the initializer range expression to an actual array
|
|
||||||
val declArraySize = decl.arraysize?.size()
|
|
||||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
|
||||||
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
|
|
||||||
val constRange = rangeExpr.toConstantIntegerRange()
|
|
||||||
if(constRange!=null) {
|
|
||||||
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
|
||||||
if(eltType in ByteDatatypes) {
|
|
||||||
decl.value = ArrayLiteralValue(decl.datatype,
|
|
||||||
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
|
||||||
position = decl.value!!.position)
|
|
||||||
} else {
|
|
||||||
decl.value = ArrayLiteralValue(decl.datatype,
|
|
||||||
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
|
||||||
position = decl.value!!.position)
|
|
||||||
}
|
|
||||||
decl.value!!.linkParents(decl)
|
|
||||||
optimizationsDone++
|
|
||||||
return super.visit(decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
|
||||||
errors.add(ExpressionError("arraysize requires only integers here", numericLv.position))
|
|
||||||
val size = decl.arraysize?.size() ?: return decl
|
|
||||||
if (rangeExpr==null && numericLv!=null) {
|
|
||||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
|
||||||
val fillvalue = numericLv.number.toInt()
|
|
||||||
when(decl.datatype){
|
|
||||||
DataType.ARRAY_UB -> {
|
|
||||||
if(fillvalue !in 0..255)
|
|
||||||
errors.add(ExpressionError("ubyte value overflow", numericLv.position))
|
|
||||||
}
|
|
||||||
DataType.ARRAY_B -> {
|
|
||||||
if(fillvalue !in -128..127)
|
|
||||||
errors.add(ExpressionError("byte value overflow", numericLv.position))
|
|
||||||
}
|
|
||||||
DataType.ARRAY_UW -> {
|
|
||||||
if(fillvalue !in 0..65535)
|
|
||||||
errors.add(ExpressionError("uword value overflow", numericLv.position))
|
|
||||||
}
|
|
||||||
DataType.ARRAY_W -> {
|
|
||||||
if(fillvalue !in -32768..32767)
|
|
||||||
errors.add(ExpressionError("word value overflow", numericLv.position))
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
// create the array itself, filled with the fillvalue.
|
|
||||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
|
|
||||||
val refValue = ArrayLiteralValue(decl.datatype, array, position = numericLv.position)
|
|
||||||
decl.value = refValue
|
|
||||||
refValue.parent=decl
|
|
||||||
optimizationsDone++
|
|
||||||
return super.visit(decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.ARRAY_F -> {
|
|
||||||
val size = decl.arraysize?.size() ?: return decl
|
|
||||||
val litval = decl.value as? NumericLiteralValue
|
|
||||||
if(litval==null) {
|
|
||||||
// there's no initialization value, but the size is known, so we're ok.
|
|
||||||
return super.visit(decl)
|
|
||||||
} else {
|
|
||||||
// arraysize initializer is a single int, and we know the size.
|
|
||||||
val fillvalue = litval.number.toDouble()
|
|
||||||
if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
|
|
||||||
errors.add(ExpressionError("float value overflow", litval.position))
|
|
||||||
else {
|
|
||||||
// create the array itself, filled with the fillvalue.
|
|
||||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
|
|
||||||
val refValue = ArrayLiteralValue(DataType.ARRAY_F, array, position = litval.position)
|
|
||||||
decl.value = refValue
|
|
||||||
refValue.parent=decl
|
|
||||||
optimizationsDone++
|
|
||||||
return super.visit(decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
// nothing to do for this type
|
|
||||||
// this includes strings and structs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.visit(decl)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
|
|
||||||
*/
|
|
||||||
override fun visit(identifier: IdentifierReference): Expression {
|
|
||||||
// don't replace when it's an assignment target or loop variable
|
|
||||||
if(identifier.parent is AssignTarget)
|
|
||||||
return identifier
|
|
||||||
var forloop = identifier.parent as? ForLoop
|
|
||||||
if(forloop==null)
|
|
||||||
forloop = identifier.parent.parent as? ForLoop
|
|
||||||
if(forloop!=null && identifier===forloop.loopVar)
|
|
||||||
return identifier
|
|
||||||
|
|
||||||
return try {
|
|
||||||
val cval = identifier.constValue(program) ?: return identifier
|
|
||||||
return when (cval.type) {
|
|
||||||
in NumericDatatypes -> {
|
|
||||||
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
|
|
||||||
copy.parent = identifier.parent
|
|
||||||
copy
|
|
||||||
}
|
|
||||||
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
|
|
||||||
else -> identifier
|
|
||||||
}
|
|
||||||
} catch (ax: AstException) {
|
|
||||||
addError(ax)
|
|
||||||
identifier
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): Expression {
|
|
||||||
super.visit(functionCall)
|
|
||||||
typeCastConstArguments(functionCall)
|
|
||||||
return try {
|
|
||||||
functionCall.constValue(program) ?: functionCall
|
|
||||||
} catch (ax: AstException) {
|
|
||||||
addError(ax)
|
|
||||||
functionCall
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
|
||||||
super.visit(functionCallStatement)
|
|
||||||
typeCastConstArguments(functionCallStatement)
|
|
||||||
return functionCallStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun typeCastConstArguments(functionCall: IFunctionCall) {
|
|
||||||
if(functionCall.target.nameInSource.size==1) {
|
|
||||||
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
|
|
||||||
if(builtinFunction!=null) {
|
|
||||||
// match the arguments of a builtin function signature.
|
|
||||||
for(arg in functionCall.args.withIndex().zip(builtinFunction.parameters)) {
|
|
||||||
val possibleDts = arg.second.possibleDatatypes
|
|
||||||
val argConst = arg.first.value.constValue(program)
|
|
||||||
if(argConst!=null && argConst.type !in possibleDts) {
|
|
||||||
val convertedValue = argConst.cast(possibleDts.first())
|
|
||||||
functionCall.args[arg.first.index] = convertedValue
|
|
||||||
optimizationsDone++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// match the arguments of a subroutine.
|
|
||||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
|
||||||
if(subroutine!=null) {
|
|
||||||
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
|
|
||||||
for(arg in functionCall.args.withIndex().zip(subroutine.parameters)) {
|
|
||||||
val expectedDt = arg.second.type
|
|
||||||
val argConst = arg.first.value.constValue(program)
|
|
||||||
if(argConst!=null && argConst.type!=expectedDt) {
|
|
||||||
val convertedValue = argConst.cast(expectedDt)
|
|
||||||
functionCall.args[arg.first.index] = convertedValue
|
|
||||||
optimizationsDone++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(memread: DirectMemoryRead): Expression {
|
|
||||||
// @( &thing ) --> thing
|
|
||||||
val addrOf = memread.addressExpression as? AddressOf
|
|
||||||
if(addrOf!=null)
|
|
||||||
return super.visit(addrOf.identifier)
|
|
||||||
return super.visit(memread)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to accept a unary prefix expression.
|
|
||||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
|
||||||
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
|
||||||
*/
|
|
||||||
override fun visit(expr: PrefixExpression): Expression {
|
|
||||||
return try {
|
|
||||||
val prefixExpr=super.visit(expr)
|
|
||||||
if(prefixExpr !is PrefixExpression)
|
|
||||||
return prefixExpr
|
|
||||||
|
|
||||||
val subexpr = prefixExpr.expression
|
|
||||||
if (subexpr is NumericLiteralValue) {
|
|
||||||
// accept prefixed literal values (such as -3, not true)
|
|
||||||
return when (prefixExpr.operator) {
|
|
||||||
"+" -> subexpr
|
|
||||||
"-" -> when (subexpr.type) {
|
|
||||||
in IntegerDatatypes -> {
|
|
||||||
optimizationsDone++
|
|
||||||
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
optimizationsDone++
|
|
||||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position)
|
|
||||||
}
|
|
||||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
|
||||||
}
|
|
||||||
"~" -> when (subexpr.type) {
|
|
||||||
in IntegerDatatypes -> {
|
|
||||||
optimizationsDone++
|
|
||||||
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
|
|
||||||
}
|
|
||||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
|
||||||
}
|
|
||||||
"not" -> {
|
|
||||||
optimizationsDone++
|
|
||||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
|
|
||||||
}
|
|
||||||
else -> throw ExpressionError(prefixExpr.operator, subexpr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prefixExpr
|
|
||||||
} catch (ax: AstException) {
|
|
||||||
addError(ax)
|
|
||||||
expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to accept a binary expression.
|
|
||||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
|
||||||
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
|
||||||
*
|
|
||||||
* More complex stuff: reordering to group constants:
|
|
||||||
* If one of our operands is a Constant,
|
|
||||||
* and the other operand is a Binary expression,
|
|
||||||
* and one of ITS operands is a Constant,
|
|
||||||
* and ITS other operand is NOT a Constant,
|
|
||||||
* ...it may be possible to rewrite the expression to group the two Constants together,
|
|
||||||
* to allow them to be const-folded away.
|
|
||||||
*
|
|
||||||
* examples include:
|
|
||||||
* (X / c1) * c2 -> X / (c2/c1)
|
|
||||||
* (X + c1) - c2 -> X + (c1-c2)
|
|
||||||
*/
|
|
||||||
override fun visit(expr: BinaryExpression): Expression {
|
|
||||||
return try {
|
|
||||||
super.visit(expr)
|
|
||||||
|
|
||||||
if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
|
|
||||||
|| expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
|
|
||||||
throw FatalAstException("binexpr with reference litval instead of numeric")
|
|
||||||
|
|
||||||
val leftconst = expr.left.constValue(program)
|
|
||||||
val rightconst = expr.right.constValue(program)
|
|
||||||
|
|
||||||
val subExpr: BinaryExpression? = when {
|
|
||||||
leftconst!=null -> expr.right as? BinaryExpression
|
|
||||||
rightconst!=null -> expr.left as? BinaryExpression
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
if(subExpr!=null) {
|
|
||||||
val subleftconst = subExpr.left.constValue(program)
|
|
||||||
val subrightconst = subExpr.right.constValue(program)
|
|
||||||
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
|
|
||||||
// try reordering.
|
|
||||||
return groupTwoConstsTogether(expr, subExpr,
|
|
||||||
leftconst != null, rightconst != null,
|
|
||||||
subleftconst != null, subrightconst != null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// const fold when both operands are a const
|
|
||||||
return when {
|
|
||||||
leftconst != null && rightconst != null -> {
|
|
||||||
optimizationsDone++
|
|
||||||
val evaluator = ConstExprEvaluator()
|
|
||||||
evaluator.evaluate(leftconst, expr.operator, rightconst)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> expr
|
|
||||||
}
|
|
||||||
} catch (ax: AstException) {
|
|
||||||
addError(ax)
|
|
||||||
expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun groupTwoConstsTogether(expr: BinaryExpression,
|
|
||||||
subExpr: BinaryExpression,
|
|
||||||
leftIsConst: Boolean,
|
|
||||||
rightIsConst: Boolean,
|
|
||||||
subleftIsConst: Boolean,
|
|
||||||
subrightIsConst: Boolean): Expression
|
|
||||||
{
|
|
||||||
// todo: this implements only a small set of possible reorderings at this time
|
|
||||||
if(expr.operator==subExpr.operator) {
|
|
||||||
// both operators are the isSameAs.
|
|
||||||
// If + or *, we can simply swap the const of expr and Var in subexpr.
|
|
||||||
if(expr.operator=="+" || expr.operator=="*") {
|
|
||||||
if(leftIsConst) {
|
|
||||||
if(subleftIsConst)
|
|
||||||
expr.left = subExpr.right.also { subExpr.right = expr.left }
|
|
||||||
else
|
|
||||||
expr.left = subExpr.left.also { subExpr.left = expr.left }
|
|
||||||
} else {
|
|
||||||
if(subleftIsConst)
|
|
||||||
expr.right = subExpr.right.also {subExpr.right = expr.right }
|
|
||||||
else
|
|
||||||
expr.right = subExpr.left.also { subExpr.left = expr.right }
|
|
||||||
}
|
|
||||||
optimizationsDone++
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
|
|
||||||
if(expr.operator=="-" || expr.operator=="/") {
|
|
||||||
optimizationsDone++
|
|
||||||
if(leftIsConst) {
|
|
||||||
return if(subleftIsConst) {
|
|
||||||
val tmp = subExpr.right
|
|
||||||
subExpr.right = subExpr.left
|
|
||||||
subExpr.left = expr.left
|
|
||||||
expr.left = tmp
|
|
||||||
expr.operator = if(expr.operator=="-") "+" else "*"
|
|
||||||
expr
|
|
||||||
} else
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
|
||||||
expr.operator, subExpr.left, expr.position)
|
|
||||||
} else {
|
|
||||||
return if(subleftIsConst) {
|
|
||||||
expr.right = subExpr.right.also { subExpr.right = expr.right }
|
|
||||||
expr
|
|
||||||
} else
|
|
||||||
BinaryExpression(
|
|
||||||
subExpr.left, expr.operator,
|
|
||||||
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
|
||||||
expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
if(expr.operator=="/" && subExpr.operator=="*") {
|
|
||||||
optimizationsDone++
|
|
||||||
if(leftIsConst) {
|
|
||||||
return if(subleftIsConst) {
|
|
||||||
// C1/(C2*V) -> (C1/C2)/V
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
|
|
||||||
"/",
|
|
||||||
subExpr.right, expr.position)
|
|
||||||
} else {
|
|
||||||
// C1/(V*C2) -> (C1/C2)/V
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
|
|
||||||
"/",
|
|
||||||
subExpr.left, expr.position)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return if(subleftIsConst) {
|
|
||||||
// (C1*V)/C2 -> (C1/C2)*V
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
|
|
||||||
"*",
|
|
||||||
subExpr.right, expr.position)
|
|
||||||
} else {
|
|
||||||
// (V*C1)/C2 -> (C1/C2)*V
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
|
|
||||||
"*",
|
|
||||||
subExpr.left, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(expr.operator=="*" && subExpr.operator=="/") {
|
|
||||||
optimizationsDone++
|
|
||||||
if(leftIsConst) {
|
|
||||||
return if(subleftIsConst) {
|
|
||||||
// C1*(C2/V) -> (C1*C2)/V
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
|
|
||||||
"/",
|
|
||||||
subExpr.right, expr.position)
|
|
||||||
} else {
|
|
||||||
// C1*(V/C2) -> (C1/C2)*V
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
|
|
||||||
"*",
|
|
||||||
subExpr.left, expr.position)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return if(subleftIsConst) {
|
|
||||||
// (C1/V)*C2 -> (C1*C2)/V
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
|
|
||||||
"/",
|
|
||||||
subExpr.right, expr.position)
|
|
||||||
} else {
|
|
||||||
// (V/C1)*C2 -> (C1/C2)*V
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
|
|
||||||
"*",
|
|
||||||
subExpr.left, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(expr.operator=="+" && subExpr.operator=="-") {
|
|
||||||
optimizationsDone++
|
|
||||||
if(leftIsConst){
|
|
||||||
return if(subleftIsConst){
|
|
||||||
// c1+(c2-v) -> (c1+c2)-v
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
|
|
||||||
"-",
|
|
||||||
subExpr.right, expr.position)
|
|
||||||
} else {
|
|
||||||
// c1+(v-c2) -> v+(c1-c2)
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
|
|
||||||
"+",
|
|
||||||
subExpr.left, expr.position)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return if(subleftIsConst) {
|
|
||||||
// (c1-v)+c2 -> (c1+c2)-v
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
|
|
||||||
"-",
|
|
||||||
subExpr.right, expr.position)
|
|
||||||
} else {
|
|
||||||
// (v-c1)+c2 -> v+(c2-c1)
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
|
|
||||||
"+",
|
|
||||||
subExpr.left, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(expr.operator=="-" && subExpr.operator=="+") {
|
|
||||||
optimizationsDone++
|
|
||||||
if(leftIsConst) {
|
|
||||||
return if(subleftIsConst) {
|
|
||||||
// c1-(c2+v) -> (c1-c2)-v
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
|
|
||||||
"-",
|
|
||||||
subExpr.right, expr.position)
|
|
||||||
} else {
|
|
||||||
// c1-(v+c2) -> (c1-c2)-v
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
|
|
||||||
"-",
|
|
||||||
subExpr.left, expr.position)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return if(subleftIsConst) {
|
|
||||||
// (c1+v)-c2 -> v+(c1-c2)
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
|
|
||||||
"+",
|
|
||||||
subExpr.right, expr.position)
|
|
||||||
} else {
|
|
||||||
// (v+c1)-c2 -> v+(c1-c2)
|
|
||||||
BinaryExpression(
|
|
||||||
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
|
|
||||||
"+",
|
|
||||||
subExpr.left, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(forLoop: ForLoop): Statement {
|
|
||||||
|
|
||||||
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
|
|
||||||
val newFrom: NumericLiteralValue
|
|
||||||
val newTo: NumericLiteralValue
|
|
||||||
try {
|
|
||||||
newFrom = rangeFrom.cast(targetDt)
|
|
||||||
newTo = rangeTo.cast(targetDt)
|
|
||||||
} catch (x: ExpressionError) {
|
|
||||||
return range
|
|
||||||
}
|
|
||||||
val newStep: Expression = try {
|
|
||||||
stepLiteral?.cast(targetDt)?: range.step
|
|
||||||
} catch(ee: ExpressionError) {
|
|
||||||
range.step
|
|
||||||
}
|
|
||||||
return RangeExpr(newFrom, newTo, newStep, range.position)
|
|
||||||
}
|
|
||||||
|
|
||||||
val forLoop2 = super.visit(forLoop) as ForLoop
|
|
||||||
|
|
||||||
// check if we need to adjust an array literal to the loop variable's datatype
|
|
||||||
val array = forLoop2.iterable as? ArrayLiteralValue
|
|
||||||
if(array!=null) {
|
|
||||||
val loopvarDt: DataType = when {
|
|
||||||
forLoop.loopVar!=null -> forLoop.loopVar!!.inferType(program).typeOrElse(DataType.UBYTE)
|
|
||||||
forLoop.loopRegister!=null -> DataType.UBYTE
|
|
||||||
else -> throw FatalAstException("weird for loop")
|
|
||||||
}
|
|
||||||
|
|
||||||
val arrayType = when(loopvarDt) {
|
|
||||||
DataType.UBYTE -> DataType.ARRAY_UB
|
|
||||||
DataType.BYTE -> DataType.ARRAY_B
|
|
||||||
DataType.UWORD -> DataType.ARRAY_UW
|
|
||||||
DataType.WORD -> DataType.ARRAY_W
|
|
||||||
DataType.FLOAT -> DataType.ARRAY_F
|
|
||||||
else -> throw FatalAstException("invalid array elt type")
|
|
||||||
}
|
|
||||||
val array2 = array.cast(arrayType)
|
|
||||||
if(array2!=null && array2!==array) {
|
|
||||||
forLoop2.iterable = array2
|
|
||||||
array2.linkParents(forLoop2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
|
||||||
val iterableRange = forLoop2.iterable as? RangeExpr ?: return forLoop2
|
|
||||||
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
|
||||||
val rangeTo = iterableRange.to as? NumericLiteralValue
|
|
||||||
if(rangeFrom==null || rangeTo==null) return forLoop2
|
|
||||||
|
|
||||||
val loopvar = forLoop2.loopVar?.targetVarDecl(program.namespace)
|
|
||||||
if(loopvar!=null) {
|
|
||||||
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
|
||||||
when(loopvar.datatype) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
if(rangeFrom.type!= DataType.UBYTE) {
|
|
||||||
// attempt to translate the iterable into ubyte values
|
|
||||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if(rangeFrom.type!= DataType.BYTE) {
|
|
||||||
// attempt to translate the iterable into byte values
|
|
||||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
if(rangeFrom.type!= DataType.UWORD) {
|
|
||||||
// attempt to translate the iterable into uword values
|
|
||||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
if(rangeFrom.type!= DataType.WORD) {
|
|
||||||
// attempt to translate the iterable into word values
|
|
||||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return forLoop2
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
|
||||||
val array = super.visit(arrayLiteral)
|
|
||||||
if(array is ArrayLiteralValue) {
|
|
||||||
val vardecl = array.parent as? VarDecl
|
|
||||||
return if (vardecl!=null) {
|
|
||||||
fixupArrayEltDatatypesFromVardecl(array, vardecl)
|
|
||||||
} else {
|
|
||||||
// it's not an array associated with a vardecl, attempt to guess the data type from the array values
|
|
||||||
fixupArrayEltDatatypes(array, program)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): Statement {
|
|
||||||
super.visit(assignment)
|
|
||||||
val lv = assignment.value as? NumericLiteralValue
|
|
||||||
if(lv!=null) {
|
|
||||||
// see if we can promote/convert a literal value to the required datatype
|
|
||||||
val idt = assignment.target.inferType(program, assignment)
|
|
||||||
if(!idt.isKnown)
|
|
||||||
return assignment
|
|
||||||
when(idt.typeOrElse(DataType.STRUCT)) {
|
|
||||||
DataType.UWORD -> {
|
|
||||||
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
|
|
||||||
if(lv.type== DataType.UBYTE)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
|
|
||||||
else if(lv.type== DataType.BYTE && lv.number.toInt()>=0)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
|
|
||||||
else if(lv.type== DataType.WORD && lv.number.toInt()>=0)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
|
|
||||||
else if(lv.type== DataType.FLOAT) {
|
|
||||||
val d = lv.number.toDouble()
|
|
||||||
if(floor(d)==d && d>=0 && d<=65535)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.UWORD, floor(d).toInt(), lv.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
// we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255,
|
|
||||||
if(lv.type== DataType.UWORD && lv.number.toInt() <= 255)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
|
|
||||||
else if(lv.type== DataType.BYTE && lv.number.toInt() >=0)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
|
|
||||||
else if(lv.type== DataType.FLOAT) {
|
|
||||||
val d = lv.number.toDouble()
|
|
||||||
if(floor(d)==d && d >=0 && d<=255)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.UBYTE, floor(d).toShort(), lv.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
// we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127
|
|
||||||
if(lv.type== DataType.UWORD && lv.number.toInt() <= 127)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
|
|
||||||
else if(lv.type== DataType.UBYTE && lv.number.toInt() <= 127)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
|
|
||||||
else if(lv.type== DataType.FLOAT) {
|
|
||||||
val d = lv.number.toDouble()
|
|
||||||
if(floor(d)==d && d>=0 && d<=127)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
// we can convert to WORD: any UBYTE/BYTE, UWORD <= 32767, FLOAT that's an integer -32768..32767,
|
|
||||||
if(lv.type== DataType.UBYTE || lv.type== DataType.BYTE)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
|
|
||||||
else if(lv.type== DataType.UWORD && lv.number.toInt() <= 32767)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
|
|
||||||
else if(lv.type== DataType.FLOAT) {
|
|
||||||
val d = lv.number.toDouble()
|
|
||||||
if(floor(d)==d && d>=-32768 && d<=32767)
|
|
||||||
assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
assignment.value = NumericLiteralValue(DataType.FLOAT, lv.number.toDouble(), lv.position)
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return assignment
|
|
||||||
}
|
|
||||||
}
|
|
592
compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt
Normal file
592
compiler/src/prog8/optimizer/ConstantFoldingOptimizer.kt
Normal file
@ -0,0 +1,592 @@
|
|||||||
|
package prog8.optimizer
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstModification
|
||||||
|
import prog8.ast.statements.*
|
||||||
|
import prog8.compiler.target.CompilationTarget
|
||||||
|
|
||||||
|
|
||||||
|
// First thing to do is 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>()
|
||||||
|
|
||||||
|
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||||
|
// replace identifiers that refer to const value, with the value itself
|
||||||
|
// if it's a simple type and if it's not a left hand side variable
|
||||||
|
if(identifier.parent is AssignTarget)
|
||||||
|
return noModifications
|
||||||
|
var forloop = identifier.parent as? ForLoop
|
||||||
|
if(forloop==null)
|
||||||
|
forloop = identifier.parent.parent as? ForLoop
|
||||||
|
if(forloop!=null && identifier===forloop.loopVar)
|
||||||
|
return noModifications
|
||||||
|
|
||||||
|
val cval = identifier.constValue(program) ?: return noModifications
|
||||||
|
return when (cval.type) {
|
||||||
|
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
|
||||||
|
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
|
||||||
|
else -> noModifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||||
|
errors.err("recursive var declaration", decl.position)
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||||
|
if(decl.isArray){
|
||||||
|
if(decl.arraysize==null) {
|
||||||
|
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||||
|
val arrayval = decl.value as? ArrayLiteralValue
|
||||||
|
if(arrayval!=null) {
|
||||||
|
return listOf(IAstModification.SetExpression(
|
||||||
|
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||||
|
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
|
||||||
|
decl
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(decl.arraysize?.size()==null) {
|
||||||
|
val size = decl.arraysize!!.index.constValue(program)
|
||||||
|
if(size!=null) {
|
||||||
|
return listOf(IAstModification.SetExpression(
|
||||||
|
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||||
|
size, decl
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when(decl.datatype) {
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||||
|
val litval = decl.value as? NumericLiteralValue
|
||||||
|
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||||
|
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||||
|
val numericLv = decl.value as? NumericLiteralValue
|
||||||
|
val rangeExpr = decl.value as? RangeExpr
|
||||||
|
if(rangeExpr!=null) {
|
||||||
|
// convert the initializer range expression to an actual array
|
||||||
|
val declArraySize = decl.arraysize?.size()
|
||||||
|
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||||
|
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||||
|
val constRange = rangeExpr.toConstantIntegerRange()
|
||||||
|
if(constRange!=null) {
|
||||||
|
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||||
|
val newValue = if(eltType in ByteDatatypes) {
|
||||||
|
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||||
|
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||||
|
position = decl.value!!.position)
|
||||||
|
} else {
|
||||||
|
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||||
|
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
||||||
|
position = decl.value!!.position)
|
||||||
|
}
|
||||||
|
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(numericLv!=null && numericLv.type==DataType.FLOAT)
|
||||||
|
errors.err("arraysize requires only integers here", numericLv.position)
|
||||||
|
val size = decl.arraysize?.size() ?: return noModifications
|
||||||
|
if (rangeExpr==null && numericLv!=null) {
|
||||||
|
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||||
|
val fillvalue = numericLv.number.toInt()
|
||||||
|
when(decl.datatype){
|
||||||
|
DataType.ARRAY_UB -> {
|
||||||
|
if(fillvalue !in 0..255)
|
||||||
|
errors.err("ubyte value overflow", numericLv.position)
|
||||||
|
}
|
||||||
|
DataType.ARRAY_B -> {
|
||||||
|
if(fillvalue !in -128..127)
|
||||||
|
errors.err("byte value overflow", numericLv.position)
|
||||||
|
}
|
||||||
|
DataType.ARRAY_UW -> {
|
||||||
|
if(fillvalue !in 0..65535)
|
||||||
|
errors.err("uword value overflow", numericLv.position)
|
||||||
|
}
|
||||||
|
DataType.ARRAY_W -> {
|
||||||
|
if(fillvalue !in -32768..32767)
|
||||||
|
errors.err("word value overflow", numericLv.position)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
// create the array itself, filled with the fillvalue.
|
||||||
|
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) as Expression}.toTypedArray()
|
||||||
|
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.ARRAY_F -> {
|
||||||
|
val size = decl.arraysize?.size() ?: return noModifications
|
||||||
|
val litval = decl.value as? NumericLiteralValue
|
||||||
|
val rangeExpr = decl.value as? RangeExpr
|
||||||
|
if(rangeExpr!=null) {
|
||||||
|
// convert the initializer range expression to an actual array of floats
|
||||||
|
val declArraySize = decl.arraysize?.size()
|
||||||
|
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||||
|
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||||
|
val constRange = rangeExpr.toConstantIntegerRange()
|
||||||
|
if(constRange!=null) {
|
||||||
|
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||||
|
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||||
|
position = decl.value!!.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(rangeExpr==null && litval!=null) {
|
||||||
|
// arraysize initializer is a single int, and we know the size.
|
||||||
|
val fillvalue = litval.number.toDouble()
|
||||||
|
if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
|
||||||
|
errors.err("float value overflow", litval.position)
|
||||||
|
else {
|
||||||
|
// create the array itself, filled with the fillvalue.
|
||||||
|
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
|
||||||
|
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// nothing to do for this type
|
||||||
|
// this includes strings and structs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val declValue = decl.value
|
||||||
|
if(declValue!=null && decl.type==VarDeclType.VAR
|
||||||
|
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
|
||||||
|
// cast the numeric literal to the appropriate datatype of the variable
|
||||||
|
return listOf(IAstModification.ReplaceNode(decl.value!!, declValue.cast(decl.datatype), decl))
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
|
||||||
|
private val noModifications = emptyList<IAstModification>()
|
||||||
|
|
||||||
|
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||||
|
// @( &thing ) --> thing
|
||||||
|
val addrOf = memread.addressExpression as? AddressOf
|
||||||
|
return if(addrOf!=null)
|
||||||
|
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
|
||||||
|
else
|
||||||
|
noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
// Try to turn a unary prefix expression into a single constant value.
|
||||||
|
// Compile-time constant sub expressions will be evaluated on the spot.
|
||||||
|
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||||
|
val subexpr = expr.expression
|
||||||
|
if (subexpr is NumericLiteralValue) {
|
||||||
|
// accept prefixed literal values (such as -3, not true)
|
||||||
|
return when (expr.operator) {
|
||||||
|
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
|
||||||
|
"-" -> when (subexpr.type) {
|
||||||
|
in IntegerDatatypes -> {
|
||||||
|
listOf(IAstModification.ReplaceNode(expr,
|
||||||
|
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position),
|
||||||
|
parent))
|
||||||
|
}
|
||||||
|
DataType.FLOAT -> {
|
||||||
|
listOf(IAstModification.ReplaceNode(expr,
|
||||||
|
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
|
||||||
|
parent))
|
||||||
|
}
|
||||||
|
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||||
|
}
|
||||||
|
"~" -> when (subexpr.type) {
|
||||||
|
in IntegerDatatypes -> {
|
||||||
|
listOf(IAstModification.ReplaceNode(expr,
|
||||||
|
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position),
|
||||||
|
parent))
|
||||||
|
}
|
||||||
|
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||||
|
}
|
||||||
|
"not" -> {
|
||||||
|
listOf(IAstModification.ReplaceNode(expr,
|
||||||
|
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
|
||||||
|
parent))
|
||||||
|
}
|
||||||
|
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to constfold a binary expression.
|
||||||
|
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||||
|
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
||||||
|
*
|
||||||
|
* More complex stuff: reordering to group constants:
|
||||||
|
* If one of our operands is a Constant,
|
||||||
|
* and the other operand is a Binary expression,
|
||||||
|
* and one of ITS operands is a Constant,
|
||||||
|
* and ITS other operand is NOT a Constant,
|
||||||
|
* ...it may be possible to rewrite the expression to group the two Constants together,
|
||||||
|
* to allow them to be const-folded away.
|
||||||
|
*
|
||||||
|
* examples include:
|
||||||
|
* (X / c1) * c2 -> X / (c2/c1)
|
||||||
|
* (X + c1) - c2 -> X + (c1-c2)
|
||||||
|
*/
|
||||||
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
val leftconst = expr.left.constValue(program)
|
||||||
|
val rightconst = expr.right.constValue(program)
|
||||||
|
|
||||||
|
val subExpr: BinaryExpression? = when {
|
||||||
|
leftconst!=null -> expr.right as? BinaryExpression
|
||||||
|
rightconst!=null -> expr.left as? BinaryExpression
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if(subExpr!=null) {
|
||||||
|
val subleftconst = subExpr.left.constValue(program)
|
||||||
|
val subrightconst = subExpr.right.constValue(program)
|
||||||
|
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
|
||||||
|
// try reordering.
|
||||||
|
val change = groupTwoConstsTogether(expr, subExpr,
|
||||||
|
leftconst != null, rightconst != null,
|
||||||
|
subleftconst != null, subrightconst != null)
|
||||||
|
return change?.let { listOf(it) } ?: noModifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const fold when both operands are a const
|
||||||
|
if(leftconst != null && rightconst != null) {
|
||||||
|
val evaluator = ConstExprEvaluator()
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
evaluator.evaluate(leftconst, expr.operator, rightconst),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||||
|
// because constant folding can result in arrays that are now suddenly capable
|
||||||
|
// of telling the type of all their elements (for instance, when they contained -2 which
|
||||||
|
// was a prefix expression earlier), we recalculate the array's datatype.
|
||||||
|
if(array.type.isKnown)
|
||||||
|
return noModifications
|
||||||
|
|
||||||
|
// if the array literalvalue is inside an array vardecl, take the type from that
|
||||||
|
// otherwise infer it from the elements of the array
|
||||||
|
val vardeclType = (array.parent as? VarDecl)?.datatype
|
||||||
|
if(vardeclType!=null) {
|
||||||
|
val newArray = array.cast(vardeclType)
|
||||||
|
if (newArray != null && newArray != array)
|
||||||
|
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||||
|
} else {
|
||||||
|
val arrayDt = array.guessDatatype(program)
|
||||||
|
if (arrayDt.isKnown) {
|
||||||
|
val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||||
|
if (newArray != null && newArray != array)
|
||||||
|
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
|
// the args of a fuction are constfolded via recursion already.
|
||||||
|
val constvalue = functionCall.constValue(program)
|
||||||
|
return if(constvalue!=null)
|
||||||
|
listOf(IAstModification.ReplaceNode(functionCall, constvalue, parent))
|
||||||
|
else
|
||||||
|
noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
|
||||||
|
val newFrom: NumericLiteralValue
|
||||||
|
val newTo: NumericLiteralValue
|
||||||
|
try {
|
||||||
|
newFrom = rangeFrom.cast(targetDt)
|
||||||
|
newTo = rangeTo.cast(targetDt)
|
||||||
|
} catch (x: ExpressionError) {
|
||||||
|
return range
|
||||||
|
}
|
||||||
|
val newStep: Expression = try {
|
||||||
|
stepLiteral?.cast(targetDt)?: range.step
|
||||||
|
} catch(ee: ExpressionError) {
|
||||||
|
range.step
|
||||||
|
}
|
||||||
|
return RangeExpr(newFrom, newTo, newStep, range.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||||
|
val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications
|
||||||
|
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
||||||
|
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||||
|
if(rangeFrom==null || rangeTo==null) return noModifications
|
||||||
|
|
||||||
|
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!!
|
||||||
|
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
||||||
|
when(loopvar.datatype) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
if(rangeFrom.type!= DataType.UBYTE) {
|
||||||
|
// attempt to translate the iterable into ubyte values
|
||||||
|
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||||
|
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
if(rangeFrom.type!= DataType.BYTE) {
|
||||||
|
// attempt to translate the iterable into byte values
|
||||||
|
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||||
|
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
if(rangeFrom.type!= DataType.UWORD) {
|
||||||
|
// attempt to translate the iterable into uword values
|
||||||
|
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||||
|
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
if(rangeFrom.type!= DataType.WORD) {
|
||||||
|
// attempt to translate the iterable into word values
|
||||||
|
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||||
|
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ShuffleOperands(val expr: BinaryExpression,
|
||||||
|
val exprOperator: String?,
|
||||||
|
val subExpr: BinaryExpression,
|
||||||
|
val newExprLeft: Expression?,
|
||||||
|
val newExprRight: Expression?,
|
||||||
|
val newSubexprLeft: Expression?,
|
||||||
|
val newSubexprRight: Expression?
|
||||||
|
): IAstModification {
|
||||||
|
override fun perform() {
|
||||||
|
if(exprOperator!=null) expr.operator = exprOperator
|
||||||
|
if(newExprLeft!=null) expr.left = newExprLeft
|
||||||
|
if(newExprRight!=null) expr.right = newExprRight
|
||||||
|
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
|
||||||
|
if(newSubexprRight!=null) subExpr.right = newSubexprRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun groupTwoConstsTogether(expr: BinaryExpression,
|
||||||
|
subExpr: BinaryExpression,
|
||||||
|
leftIsConst: Boolean,
|
||||||
|
rightIsConst: Boolean,
|
||||||
|
subleftIsConst: Boolean,
|
||||||
|
subrightIsConst: Boolean): IAstModification?
|
||||||
|
{
|
||||||
|
// todo: this implements only a small set of possible reorderings at this time
|
||||||
|
if(expr.operator==subExpr.operator) {
|
||||||
|
// both operators are the same.
|
||||||
|
// If + or *, we can simply shuffle the const operands around to optimize.
|
||||||
|
if(expr.operator=="+" || expr.operator=="*") {
|
||||||
|
return if(leftIsConst) {
|
||||||
|
if(subleftIsConst)
|
||||||
|
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
|
||||||
|
else
|
||||||
|
ShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
|
||||||
|
} else {
|
||||||
|
if(subleftIsConst)
|
||||||
|
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||||
|
else
|
||||||
|
ShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
|
||||||
|
if(expr.operator=="-" || expr.operator=="/") {
|
||||||
|
if(leftIsConst) {
|
||||||
|
return if (subleftIsConst) {
|
||||||
|
ShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
|
||||||
|
} else {
|
||||||
|
IAstModification.ReplaceNode(expr,
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||||
|
expr.operator, subExpr.left, expr.position),
|
||||||
|
expr.parent)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return if(subleftIsConst) {
|
||||||
|
return ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||||
|
} else {
|
||||||
|
IAstModification.ReplaceNode(expr,
|
||||||
|
BinaryExpression(
|
||||||
|
subExpr.left, expr.operator,
|
||||||
|
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||||
|
expr.position),
|
||||||
|
expr.parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
if(expr.operator=="/" && subExpr.operator=="*") {
|
||||||
|
if(leftIsConst) {
|
||||||
|
val change = if(subleftIsConst) {
|
||||||
|
// C1/(C2*V) -> (C1/C2)/V
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
|
||||||
|
"/",
|
||||||
|
subExpr.right, expr.position)
|
||||||
|
} else {
|
||||||
|
// C1/(V*C2) -> (C1/C2)/V
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
|
||||||
|
"/",
|
||||||
|
subExpr.left, expr.position)
|
||||||
|
}
|
||||||
|
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||||
|
} else {
|
||||||
|
val change = if(subleftIsConst) {
|
||||||
|
// (C1*V)/C2 -> (C1/C2)*V
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
|
||||||
|
"*",
|
||||||
|
subExpr.right, expr.position)
|
||||||
|
} else {
|
||||||
|
// (V*C1)/C2 -> (C1/C2)*V
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
|
||||||
|
"*",
|
||||||
|
subExpr.left, expr.position)
|
||||||
|
}
|
||||||
|
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(expr.operator=="*" && subExpr.operator=="/") {
|
||||||
|
if(leftIsConst) {
|
||||||
|
val change = if(subleftIsConst) {
|
||||||
|
// C1*(C2/V) -> (C1*C2)/V
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
|
||||||
|
"/",
|
||||||
|
subExpr.right, expr.position)
|
||||||
|
} else {
|
||||||
|
// C1*(V/C2) -> (C1/C2)*V
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
|
||||||
|
"*",
|
||||||
|
subExpr.left, expr.position)
|
||||||
|
}
|
||||||
|
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||||
|
} else {
|
||||||
|
val change = if(subleftIsConst) {
|
||||||
|
// (C1/V)*C2 -> (C1*C2)/V
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
|
||||||
|
"/",
|
||||||
|
subExpr.right, expr.position)
|
||||||
|
} else {
|
||||||
|
// (V/C1)*C2 -> (C1/C2)*V
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
|
||||||
|
"*",
|
||||||
|
subExpr.left, expr.position)
|
||||||
|
}
|
||||||
|
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(expr.operator=="+" && subExpr.operator=="-") {
|
||||||
|
if(leftIsConst){
|
||||||
|
val change = if(subleftIsConst){
|
||||||
|
// c1+(c2-v) -> (c1+c2)-v
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
|
||||||
|
"-",
|
||||||
|
subExpr.right, expr.position)
|
||||||
|
} else {
|
||||||
|
// c1+(v-c2) -> v+(c1-c2)
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
|
||||||
|
"+",
|
||||||
|
subExpr.left, expr.position)
|
||||||
|
}
|
||||||
|
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||||
|
} else {
|
||||||
|
val change = if(subleftIsConst) {
|
||||||
|
// (c1-v)+c2 -> (c1+c2)-v
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
|
||||||
|
"-",
|
||||||
|
subExpr.right, expr.position)
|
||||||
|
} else {
|
||||||
|
// (v-c1)+c2 -> v+(c2-c1)
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
|
||||||
|
"+",
|
||||||
|
subExpr.left, expr.position)
|
||||||
|
}
|
||||||
|
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(expr.operator=="-" && subExpr.operator=="+") {
|
||||||
|
if(leftIsConst) {
|
||||||
|
val change = if(subleftIsConst) {
|
||||||
|
// c1-(c2+v) -> (c1-c2)-v
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
|
||||||
|
"-",
|
||||||
|
subExpr.right, expr.position)
|
||||||
|
} else {
|
||||||
|
// c1-(v+c2) -> (c1-c2)-v
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
|
||||||
|
"-",
|
||||||
|
subExpr.left, expr.position)
|
||||||
|
}
|
||||||
|
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||||
|
} else {
|
||||||
|
val change = if(subleftIsConst) {
|
||||||
|
// (c1+v)-c2 -> v+(c1-c2)
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
|
||||||
|
"+",
|
||||||
|
subExpr.right, expr.position)
|
||||||
|
} else {
|
||||||
|
// (v+c1)-c2 -> v+(c1-c2)
|
||||||
|
BinaryExpression(
|
||||||
|
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
|
||||||
|
"+",
|
||||||
|
subExpr.left, expr.position)
|
||||||
|
}
|
||||||
|
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
668
compiler/src/prog8/optimizer/ExpressionSimplifier.kt
Normal file
668
compiler/src/prog8/optimizer/ExpressionSimplifier.kt
Normal file
@ -0,0 +1,668 @@
|
|||||||
|
package prog8.optimizer
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.base.*
|
||||||
|
import prog8.ast.expressions.*
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstModification
|
||||||
|
import prog8.ast.statements.Assignment
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.log2
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
|
/*
|
||||||
|
todo add more expression optimizations
|
||||||
|
|
||||||
|
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
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(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
if (assignment.aug_op != null)
|
||||||
|
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
val mods = mutableListOf<IAstModification>()
|
||||||
|
|
||||||
|
// try to statically convert a literal value into one of the desired type
|
||||||
|
val literal = typecast.expression as? NumericLiteralValue
|
||||||
|
if (literal != null) {
|
||||||
|
val newLiteral = literal.cast(typecast.type)
|
||||||
|
if (newLiteral !== literal)
|
||||||
|
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral, typecast)
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove redundant nested typecasts:
|
||||||
|
// if the typecast casts a value to the same type, remove the cast.
|
||||||
|
// if the typecast contains another typecast, remove the inner typecast.
|
||||||
|
val subTypecast = typecast.expression as? TypecastExpression
|
||||||
|
if (subTypecast != null) {
|
||||||
|
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
||||||
|
} else {
|
||||||
|
if (typecast.expression.inferType(program).istype(typecast.type))
|
||||||
|
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
if (expr.operator == "+") {
|
||||||
|
// +X --> X
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
|
||||||
|
} else if (expr.operator == "not") {
|
||||||
|
when(expr.expression) {
|
||||||
|
is PrefixExpression -> {
|
||||||
|
// NOT(NOT(...)) -> ...
|
||||||
|
val pe = expr.expression as PrefixExpression
|
||||||
|
if(pe.operator == "not")
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, pe.expression, parent))
|
||||||
|
}
|
||||||
|
is BinaryExpression -> {
|
||||||
|
// NOT (xxxx) -> invert the xxxx
|
||||||
|
val be = expr.expression as BinaryExpression
|
||||||
|
val newExpr = when (be.operator) {
|
||||||
|
"<" -> BinaryExpression(be.left, ">=", be.right, be.position)
|
||||||
|
">" -> BinaryExpression(be.left, "<=", be.right, be.position)
|
||||||
|
"<=" -> BinaryExpression(be.left, ">", be.right, be.position)
|
||||||
|
">=" -> BinaryExpression(be.left, "<", be.right, be.position)
|
||||||
|
"==" -> BinaryExpression(be.left, "!=", be.right, be.position)
|
||||||
|
"!=" -> BinaryExpression(be.left, "==", be.right, be.position)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newExpr != null)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||||
|
}
|
||||||
|
else -> return noModifications
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||||
|
val leftVal = expr.left.constValue(program)
|
||||||
|
val rightVal = expr.right.constValue(program)
|
||||||
|
|
||||||
|
val leftIDt = expr.left.inferType(program)
|
||||||
|
val rightIDt = expr.right.inferType(program)
|
||||||
|
if (!leftIDt.isKnown || !rightIDt.isKnown)
|
||||||
|
throw FatalAstException("can't determine datatype of both expression operands $expr")
|
||||||
|
|
||||||
|
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||||
|
if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
|
||||||
|
return listOf(IAstModification.SwapOperands(expr))
|
||||||
|
|
||||||
|
// X + (-A) --> X - A
|
||||||
|
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
BinaryExpression(expr.left, "-", (expr.right as PrefixExpression).expression, expr.position),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// (-A) + X --> X - A
|
||||||
|
if (expr.operator == "+" && (expr.left as? PrefixExpression)?.operator == "-") {
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
BinaryExpression(expr.right, "-", (expr.left as PrefixExpression).expression, expr.position),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
// X - (-A) --> X + A
|
||||||
|
if (expr.operator == "-" && (expr.right as? PrefixExpression)?.operator == "-") {
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
BinaryExpression(expr.left, "+", (expr.right as PrefixExpression).expression, expr.position),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
|
||||||
|
if (expr.operator == "+" || expr.operator == "-"
|
||||||
|
&& leftVal == null && rightVal == null
|
||||||
|
&& leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
|
||||||
|
val leftBinExpr = expr.left as? BinaryExpression
|
||||||
|
val rightBinExpr = expr.right as? BinaryExpression
|
||||||
|
if (leftBinExpr?.operator == "*") {
|
||||||
|
if (expr.operator == "+") {
|
||||||
|
// Y*X + X -> X*(Y + 1)
|
||||||
|
// X*Y + X -> X*(Y + 1)
|
||||||
|
val x = expr.right
|
||||||
|
val y = determineY(x, leftBinExpr)
|
||||||
|
if (y != null) {
|
||||||
|
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
|
||||||
|
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Y*X - X -> X*(Y - 1)
|
||||||
|
// X*Y - X -> X*(Y - 1)
|
||||||
|
val x = expr.right
|
||||||
|
val y = determineY(x, leftBinExpr)
|
||||||
|
if (y != null) {
|
||||||
|
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
|
||||||
|
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (rightBinExpr?.operator == "*") {
|
||||||
|
if (expr.operator == "+") {
|
||||||
|
// X + Y*X -> X*(Y + 1)
|
||||||
|
// X + X*Y -> X*(Y + 1)
|
||||||
|
val x = expr.left
|
||||||
|
val y = determineY(x, rightBinExpr)
|
||||||
|
if (y != null) {
|
||||||
|
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue.optimalInteger(1, y.position), y.position)
|
||||||
|
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(expr.operator == ">=" && rightVal?.number == 0) {
|
||||||
|
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||||
|
// unsigned >= 0 --> true
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
||||||
|
}
|
||||||
|
when(leftDt) {
|
||||||
|
DataType.BYTE -> {
|
||||||
|
// signed >=0 --> signed ^ $80
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
// signedw >=0 --> msb(signedw) ^ $80
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||||
|
mutableListOf(expr.left),
|
||||||
|
expr.position
|
||||||
|
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(expr.operator == "<" && rightVal?.number == 0) {
|
||||||
|
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||||
|
// unsigned < 0 --> false
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
||||||
|
}
|
||||||
|
when(leftDt) {
|
||||||
|
DataType.BYTE -> {
|
||||||
|
// signed < 0 --> signed & $80
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
// signedw < 0 --> msb(signedw) & $80
|
||||||
|
return listOf(IAstModification.ReplaceNode(
|
||||||
|
expr,
|
||||||
|
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||||
|
mutableListOf(expr.left),
|
||||||
|
expr.position
|
||||||
|
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||||
|
parent
|
||||||
|
))
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// simplify when a term is constant and directly determines the outcome
|
||||||
|
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
|
||||||
|
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||||
|
val newExpr: Expression? = when (expr.operator) {
|
||||||
|
"or" -> {
|
||||||
|
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue))
|
||||||
|
constTrue
|
||||||
|
else if (leftVal != null && !leftVal.asBooleanValue)
|
||||||
|
expr.right
|
||||||
|
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||||
|
expr.left
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
"and" -> {
|
||||||
|
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue))
|
||||||
|
constFalse
|
||||||
|
else if (leftVal != null && leftVal.asBooleanValue)
|
||||||
|
expr.right
|
||||||
|
else if (rightVal != null && rightVal.asBooleanValue)
|
||||||
|
expr.left
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
"xor" -> {
|
||||||
|
if (leftVal != null && !leftVal.asBooleanValue)
|
||||||
|
expr.right
|
||||||
|
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||||
|
expr.left
|
||||||
|
else if (leftVal != null && leftVal.asBooleanValue)
|
||||||
|
PrefixExpression("not", expr.right, expr.right.position)
|
||||||
|
else if (rightVal != null && rightVal.asBooleanValue)
|
||||||
|
PrefixExpression("not", expr.left, expr.left.position)
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
"|", "^" -> {
|
||||||
|
if (leftVal != null && !leftVal.asBooleanValue)
|
||||||
|
expr.right
|
||||||
|
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||||
|
expr.left
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
"&" -> {
|
||||||
|
if (leftVal != null && !leftVal.asBooleanValue)
|
||||||
|
constFalse
|
||||||
|
else if (rightVal != null && !rightVal.asBooleanValue)
|
||||||
|
constFalse
|
||||||
|
else
|
||||||
|
null
|
||||||
|
}
|
||||||
|
"*" -> optimizeMultiplication(expr, leftVal, rightVal)
|
||||||
|
"/" -> optimizeDivision(expr, leftVal, rightVal)
|
||||||
|
"+" -> optimizeAdd(expr, leftVal, rightVal)
|
||||||
|
"-" -> optimizeSub(expr, leftVal, rightVal)
|
||||||
|
"**" -> optimizePower(expr, leftVal, rightVal)
|
||||||
|
"%" -> optimizeRemainder(expr, leftVal, rightVal)
|
||||||
|
">>" -> optimizeShiftRight(expr, rightVal)
|
||||||
|
"<<" -> optimizeShiftLeft(expr, rightVal)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if(newExpr != null)
|
||||||
|
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
||||||
|
return when {
|
||||||
|
subBinExpr.left isSameAs x -> subBinExpr.right
|
||||||
|
subBinExpr.right isSameAs x -> subBinExpr.left
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||||
|
if(expr.left.isSameAs(expr.right)) {
|
||||||
|
// optimize X+X into X *2
|
||||||
|
expr.operator = "*"
|
||||||
|
expr.right = NumericLiteralValue.optimalInteger(2, expr.right.position)
|
||||||
|
expr.right.linkParents(expr)
|
||||||
|
return expr
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftVal == null && rightVal == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
val (expr2, _, rightVal2) = reorderAssociative(expr, leftVal)
|
||||||
|
if (rightVal2 != null) {
|
||||||
|
// right value is a constant, see if we can optimize
|
||||||
|
val rightConst: NumericLiteralValue = rightVal2
|
||||||
|
when (rightConst.number.toDouble()) {
|
||||||
|
0.0 -> {
|
||||||
|
// left
|
||||||
|
return expr2.left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no need to check for left val constant (because of associativity)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||||
|
if(expr.left.isSameAs(expr.right)) {
|
||||||
|
// optimize X-X into 0
|
||||||
|
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftVal == null && rightVal == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
if (rightVal != null) {
|
||||||
|
// right value is a constant, see if we can optimize
|
||||||
|
val rightConst: NumericLiteralValue = rightVal
|
||||||
|
when (rightConst.number.toDouble()) {
|
||||||
|
0.0 -> {
|
||||||
|
// left
|
||||||
|
return expr.left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (leftVal != null) {
|
||||||
|
// left value is a constant, see if we can optimize
|
||||||
|
when (leftVal.number.toDouble()) {
|
||||||
|
0.0 -> {
|
||||||
|
// -right
|
||||||
|
return PrefixExpression("-", expr.right, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||||
|
if (leftVal == null && rightVal == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
if (rightVal != null) {
|
||||||
|
// right value is a constant, see if we can optimize
|
||||||
|
val rightConst: NumericLiteralValue = rightVal
|
||||||
|
when (rightConst.number.toDouble()) {
|
||||||
|
-3.0 -> {
|
||||||
|
// -1/(left*left*left)
|
||||||
|
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||||
|
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
|
||||||
|
expr.position)
|
||||||
|
}
|
||||||
|
-2.0 -> {
|
||||||
|
// -1/(left*left)
|
||||||
|
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||||
|
BinaryExpression(expr.left, "*", expr.left, expr.position),
|
||||||
|
expr.position)
|
||||||
|
}
|
||||||
|
-1.0 -> {
|
||||||
|
// -1/left
|
||||||
|
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
||||||
|
expr.left, expr.position)
|
||||||
|
}
|
||||||
|
0.0 -> {
|
||||||
|
// 1
|
||||||
|
return NumericLiteralValue(rightConst.type, 1, expr.position)
|
||||||
|
}
|
||||||
|
0.5 -> {
|
||||||
|
// sqrt(left)
|
||||||
|
return FunctionCall(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
|
}
|
||||||
|
1.0 -> {
|
||||||
|
// left
|
||||||
|
return expr.left
|
||||||
|
}
|
||||||
|
2.0 -> {
|
||||||
|
// left*left
|
||||||
|
return BinaryExpression(expr.left, "*", expr.left, expr.position)
|
||||||
|
}
|
||||||
|
3.0 -> {
|
||||||
|
// left*left*left
|
||||||
|
return BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (leftVal != null) {
|
||||||
|
// left value is a constant, see if we can optimize
|
||||||
|
when (leftVal.number.toDouble()) {
|
||||||
|
-1.0 -> {
|
||||||
|
// -1
|
||||||
|
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
|
||||||
|
}
|
||||||
|
0.0 -> {
|
||||||
|
// 0
|
||||||
|
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
||||||
|
}
|
||||||
|
1.0 -> {
|
||||||
|
//1
|
||||||
|
return NumericLiteralValue(leftVal.type, 1, expr.position)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||||
|
if (leftVal == null && rightVal == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
// simplify assignments A = B <operator> C
|
||||||
|
|
||||||
|
val cv = rightVal?.number?.toInt()?.toDouble()
|
||||||
|
when (expr.operator) {
|
||||||
|
"%" -> {
|
||||||
|
if (cv == 1.0) {
|
||||||
|
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
|
||||||
|
} else if (cv == 2.0) {
|
||||||
|
expr.operator = "&"
|
||||||
|
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||||
|
if (leftVal == null && rightVal == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
// cannot shuffle assiciativity with division!
|
||||||
|
if (rightVal != null) {
|
||||||
|
// right value is a constant, see if we can optimize
|
||||||
|
val rightConst: NumericLiteralValue = rightVal
|
||||||
|
val cv = rightConst.number.toDouble()
|
||||||
|
val leftIDt = expr.left.inferType(program)
|
||||||
|
if (!leftIDt.isKnown)
|
||||||
|
return null
|
||||||
|
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
when (cv) {
|
||||||
|
-1.0 -> {
|
||||||
|
// '/' -> -left
|
||||||
|
if (expr.operator == "/") {
|
||||||
|
return PrefixExpression("-", expr.left, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1.0 -> {
|
||||||
|
// '/' -> left
|
||||||
|
if (expr.operator == "/") {
|
||||||
|
return expr.left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in powersOfTwo -> {
|
||||||
|
if (leftDt in IntegerDatatypes) {
|
||||||
|
// divided by a power of two => shift right
|
||||||
|
val numshifts = log2(cv).toInt()
|
||||||
|
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in negativePowersOfTwo -> {
|
||||||
|
if (leftDt in IntegerDatatypes) {
|
||||||
|
// divided by a negative power of two => negate, then shift right
|
||||||
|
val numshifts = log2(-cv).toInt()
|
||||||
|
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftDt == DataType.UBYTE) {
|
||||||
|
if (abs(rightConst.number.toDouble()) >= 256.0) {
|
||||||
|
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||||
|
}
|
||||||
|
} else if (leftDt == DataType.UWORD) {
|
||||||
|
if (abs(rightConst.number.toDouble()) >= 65536.0) {
|
||||||
|
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftVal != null) {
|
||||||
|
// left value is a constant, see if we can optimize
|
||||||
|
when (leftVal.number.toDouble()) {
|
||||||
|
0.0 -> {
|
||||||
|
// 0
|
||||||
|
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeMultiplication(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
|
||||||
|
if (leftVal == null && rightVal == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
val (expr2, _, rightVal2) = reorderAssociative(expr, leftVal)
|
||||||
|
if (rightVal2 != null) {
|
||||||
|
// right value is a constant, see if we can optimize
|
||||||
|
val leftValue: Expression = expr2.left
|
||||||
|
val rightConst: NumericLiteralValue = rightVal2
|
||||||
|
when (val cv = rightConst.number.toDouble()) {
|
||||||
|
-1.0 -> {
|
||||||
|
// -left
|
||||||
|
return PrefixExpression("-", leftValue, expr.position)
|
||||||
|
}
|
||||||
|
0.0 -> {
|
||||||
|
// 0
|
||||||
|
return NumericLiteralValue(rightConst.type, 0, expr.position)
|
||||||
|
}
|
||||||
|
1.0 -> {
|
||||||
|
// left
|
||||||
|
return expr2.left
|
||||||
|
}
|
||||||
|
in powersOfTwo -> {
|
||||||
|
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
|
||||||
|
// times a power of two => shift left
|
||||||
|
val numshifts = log2(cv).toInt()
|
||||||
|
return BinaryExpression(expr2.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in negativePowersOfTwo -> {
|
||||||
|
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
|
||||||
|
// times a negative power of two => negate, then shift left
|
||||||
|
val numshifts = log2(-cv).toInt()
|
||||||
|
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// no need to check for left val constant (because of associativity)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression? {
|
||||||
|
if (amountLv == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
val amount = amountLv.number.toInt()
|
||||||
|
if (amount == 0) {
|
||||||
|
return expr.left
|
||||||
|
}
|
||||||
|
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
|
||||||
|
when (targetDt) {
|
||||||
|
DataType.UBYTE, DataType.BYTE -> {
|
||||||
|
if (amount >= 8) {
|
||||||
|
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD, DataType.WORD -> {
|
||||||
|
if (amount >= 16) {
|
||||||
|
return NumericLiteralValue(targetDt, 0, expr.position)
|
||||||
|
} else if (amount >= 8) {
|
||||||
|
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
|
||||||
|
if (amount == 8) {
|
||||||
|
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position)
|
||||||
|
}
|
||||||
|
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||||
|
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression? {
|
||||||
|
if (amountLv == null)
|
||||||
|
return null
|
||||||
|
|
||||||
|
val amount = amountLv.number.toInt()
|
||||||
|
if (amount == 0) {
|
||||||
|
return expr.left
|
||||||
|
}
|
||||||
|
when (expr.left.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||||
|
DataType.UBYTE -> {
|
||||||
|
if (amount >= 8) {
|
||||||
|
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.BYTE -> {
|
||||||
|
if (amount > 8) {
|
||||||
|
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.UWORD -> {
|
||||||
|
if (amount >= 16) {
|
||||||
|
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||||
|
} else if (amount >= 8) {
|
||||||
|
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
||||||
|
if (amount == 8)
|
||||||
|
return msb
|
||||||
|
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataType.WORD -> {
|
||||||
|
if (amount > 16) {
|
||||||
|
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
|
||||||
|
return null
|
||||||
|
} else if (amount >= 8) {
|
||||||
|
val msbAsByte = TypecastExpression(
|
||||||
|
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
|
||||||
|
DataType.BYTE,
|
||||||
|
true, expr.position)
|
||||||
|
if (amount == 8)
|
||||||
|
return msbAsByte
|
||||||
|
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reorderAssociative(expr: BinaryExpression, leftVal: NumericLiteralValue?): ReorderedAssociativeBinaryExpr {
|
||||||
|
if (expr.operator in associativeOperators && leftVal != null) {
|
||||||
|
// swap left and right so that right is always the constant
|
||||||
|
val tmp = expr.left
|
||||||
|
expr.left = expr.right
|
||||||
|
expr.right = tmp
|
||||||
|
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(program), leftVal)
|
||||||
|
}
|
||||||
|
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
|
||||||
|
|
||||||
|
}
|
@ -1,42 +1,44 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.AstException
|
import prog8.ast.base.ErrorReporter
|
||||||
import prog8.parser.ParsingFailedError
|
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.constantFold() {
|
internal fun Program.constantFold(errors: ErrorReporter) {
|
||||||
val optimizer = ConstantFolding(this)
|
val replacer = ConstantIdentifierReplacer(this, errors)
|
||||||
try {
|
replacer.visit(this)
|
||||||
|
if(errors.isEmpty()) {
|
||||||
|
replacer.applyModifications()
|
||||||
|
|
||||||
|
val optimizer = ConstantFoldingOptimizer(this, errors)
|
||||||
optimizer.visit(this)
|
optimizer.visit(this)
|
||||||
} catch (ax: AstException) {
|
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
|
||||||
optimizer.addError(ax)
|
|
||||||
}
|
|
||||||
|
|
||||||
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
|
|
||||||
optimizer.optimizationsDone = 0
|
|
||||||
optimizer.visit(this)
|
optimizer.visit(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(optimizer.errors.isNotEmpty()) {
|
if(errors.isEmpty()) {
|
||||||
optimizer.errors.forEach { System.err.println(it) }
|
replacer.visit(this)
|
||||||
throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
|
replacer.applyModifications()
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(errors.isEmpty())
|
||||||
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
internal fun Program.optimizeStatements(): Int {
|
internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
|
||||||
val optimizer = StatementOptimizer(this)
|
val optimizer = StatementOptimizer(this, errors)
|
||||||
optimizer.visit(this)
|
optimizer.visit(this)
|
||||||
|
val optimizationCount = optimizer.applyModifications()
|
||||||
|
|
||||||
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
||||||
|
|
||||||
return optimizer.optimizationsDone
|
return optimizationCount
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun Program.simplifyExpressions() : Int {
|
internal fun Program.simplifyExpressions() : Int {
|
||||||
val optimizer = SimplifyExpressions(this)
|
val opti = ExpressionSimplifier(this)
|
||||||
optimizer.visit(this)
|
opti.visit(this)
|
||||||
return optimizer.optimizationsDone
|
return opti.applyModifications()
|
||||||
}
|
}
|
||||||
|
@ -1,828 +0,0 @@
|
|||||||
package prog8.optimizer
|
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.*
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
|
||||||
import prog8.ast.statements.Assignment
|
|
||||||
import prog8.ast.statements.Statement
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.log2
|
|
||||||
import kotlin.math.pow
|
|
||||||
|
|
||||||
/*
|
|
||||||
todo add more expression optimizations
|
|
||||||
|
|
||||||
Also see https://egorbo.com/peephole-optimizations.html
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
internal class SimplifyExpressions(private val program: Program) : IAstModifyingVisitor {
|
|
||||||
var optimizationsDone: Int = 0
|
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): Statement {
|
|
||||||
if (assignment.aug_op != null)
|
|
||||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
|
|
||||||
return super.visit(assignment)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(memread: DirectMemoryRead): Expression {
|
|
||||||
// @( &thing ) --> thing
|
|
||||||
val addrOf = memread.addressExpression as? AddressOf
|
|
||||||
if(addrOf!=null)
|
|
||||||
return super.visit(addrOf.identifier)
|
|
||||||
return super.visit(memread)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(typecast: TypecastExpression): Expression {
|
|
||||||
var tc = typecast
|
|
||||||
|
|
||||||
// try to statically convert a literal value into one of the desired type
|
|
||||||
val literal = tc.expression as? NumericLiteralValue
|
|
||||||
if(literal!=null) {
|
|
||||||
val newLiteral = literal.cast(tc.type)
|
|
||||||
if(newLiteral!==literal) {
|
|
||||||
optimizationsDone++
|
|
||||||
return newLiteral
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove redundant typecasts
|
|
||||||
while(true) {
|
|
||||||
val expr = tc.expression
|
|
||||||
if(expr !is TypecastExpression || expr.type!=tc.type) {
|
|
||||||
val assignment = typecast.parent as? Assignment
|
|
||||||
if(assignment!=null) {
|
|
||||||
val targetDt = assignment.target.inferType(program, assignment)
|
|
||||||
if(tc.expression.inferType(program)==targetDt) {
|
|
||||||
optimizationsDone++
|
|
||||||
return tc.expression
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val subTc = tc.expression as? TypecastExpression
|
|
||||||
if(subTc!=null) {
|
|
||||||
// if the previous typecast was casting to a 'bigger' type, just ignore that one
|
|
||||||
// if the previous typecast was casting to a similar type, ignore that one
|
|
||||||
if(subTc.type largerThan tc.type || subTc.type equalsSize tc.type) {
|
|
||||||
subTc.type = tc.type
|
|
||||||
subTc.parent = tc.parent
|
|
||||||
optimizationsDone++
|
|
||||||
return subTc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.visit(tc)
|
|
||||||
}
|
|
||||||
|
|
||||||
optimizationsDone++
|
|
||||||
tc = expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(expr: PrefixExpression): Expression {
|
|
||||||
if (expr.operator == "+") {
|
|
||||||
// +X --> X
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.expression.accept(this)
|
|
||||||
} else if (expr.operator == "not") {
|
|
||||||
(expr.expression as? BinaryExpression)?.let {
|
|
||||||
// NOT (...) -> invert ...
|
|
||||||
when (it.operator) {
|
|
||||||
"<" -> {
|
|
||||||
it.operator = ">="
|
|
||||||
optimizationsDone++
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
">" -> {
|
|
||||||
it.operator = "<="
|
|
||||||
optimizationsDone++
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
"<=" -> {
|
|
||||||
it.operator = ">"
|
|
||||||
optimizationsDone++
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
">=" -> {
|
|
||||||
it.operator = "<"
|
|
||||||
optimizationsDone++
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
"==" -> {
|
|
||||||
it.operator = "!="
|
|
||||||
optimizationsDone++
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
"!=" -> {
|
|
||||||
it.operator = "=="
|
|
||||||
optimizationsDone++
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(expr)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(expr: BinaryExpression): Expression {
|
|
||||||
super.visit(expr)
|
|
||||||
val leftVal = expr.left.constValue(program)
|
|
||||||
val rightVal = expr.right.constValue(program)
|
|
||||||
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
|
|
||||||
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
|
||||||
|
|
||||||
val leftIDt = expr.left.inferType(program)
|
|
||||||
val rightIDt = expr.right.inferType(program)
|
|
||||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
|
||||||
throw FatalAstException("can't determine datatype of both expression operands $expr")
|
|
||||||
|
|
||||||
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
|
||||||
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
|
|
||||||
if (leftDt != rightDt) {
|
|
||||||
// try to convert a datatype into the other (where ddd
|
|
||||||
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value <associativeoperator> X --> X <associativeoperator> Value
|
|
||||||
if (leftVal != null && expr.operator in associativeOperators && rightVal == null) {
|
|
||||||
val tmp = expr.left
|
|
||||||
expr.left = expr.right
|
|
||||||
expr.right = tmp
|
|
||||||
optimizationsDone++
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
// X + (-A) --> X - A
|
|
||||||
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
|
|
||||||
expr.operator = "-"
|
|
||||||
expr.right = (expr.right as PrefixExpression).expression
|
|
||||||
optimizationsDone++
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
// (-A) + X --> X - A
|
|
||||||
if (expr.operator == "+" && (expr.left as? PrefixExpression)?.operator == "-") {
|
|
||||||
expr.operator = "-"
|
|
||||||
val newRight = (expr.left as PrefixExpression).expression
|
|
||||||
expr.left = expr.right
|
|
||||||
expr.right = newRight
|
|
||||||
optimizationsDone++
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
// X + (-value) --> X - value
|
|
||||||
if (expr.operator == "+" && rightVal != null) {
|
|
||||||
val rv = rightVal.number.toDouble()
|
|
||||||
if (rv < 0.0) {
|
|
||||||
expr.operator = "-"
|
|
||||||
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
|
|
||||||
optimizationsDone++
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// (-value) + X --> X - value
|
|
||||||
if (expr.operator == "+" && leftVal != null) {
|
|
||||||
val lv = leftVal.number.toDouble()
|
|
||||||
if (lv < 0.0) {
|
|
||||||
expr.operator = "-"
|
|
||||||
expr.right = NumericLiteralValue(leftVal.type, -lv, leftVal.position)
|
|
||||||
optimizationsDone++
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// X - (-A) --> X + A
|
|
||||||
if (expr.operator == "-" && (expr.right as? PrefixExpression)?.operator == "-") {
|
|
||||||
expr.operator = "+"
|
|
||||||
expr.right = (expr.right as PrefixExpression).expression
|
|
||||||
optimizationsDone++
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
// X - (-value) --> X + value
|
|
||||||
if (expr.operator == "-" && rightVal != null) {
|
|
||||||
val rv = rightVal.number.toDouble()
|
|
||||||
if (rv < 0.0) {
|
|
||||||
expr.operator = "+"
|
|
||||||
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
|
|
||||||
optimizationsDone++
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr.operator == "+" || expr.operator == "-"
|
|
||||||
&& leftVal == null && rightVal == null
|
|
||||||
&& leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
|
|
||||||
val leftBinExpr = expr.left as? BinaryExpression
|
|
||||||
val rightBinExpr = expr.right as? BinaryExpression
|
|
||||||
if (leftBinExpr?.operator == "*") {
|
|
||||||
if (expr.operator == "+") {
|
|
||||||
// Y*X + X -> X*(Y - 1)
|
|
||||||
// X*Y + X -> X*(Y - 1)
|
|
||||||
val x = expr.right
|
|
||||||
val y = determineY(x, leftBinExpr)
|
|
||||||
if(y!=null) {
|
|
||||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
|
|
||||||
return BinaryExpression(x, "*", yPlus1, x.position)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Y*X - X -> X*(Y - 1)
|
|
||||||
// X*Y - X -> X*(Y - 1)
|
|
||||||
val x = expr.right
|
|
||||||
val y = determineY(x, leftBinExpr)
|
|
||||||
if(y!=null) {
|
|
||||||
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
|
|
||||||
return BinaryExpression(x, "*", yMinus1, x.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(rightBinExpr?.operator=="*") {
|
|
||||||
if(expr.operator=="+") {
|
|
||||||
// X + Y*X -> X*(Y + 1)
|
|
||||||
// X + X*Y -> X*(Y + 1)
|
|
||||||
val x = expr.left
|
|
||||||
val y = determineY(x, rightBinExpr)
|
|
||||||
if(y!=null) {
|
|
||||||
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue.optimalInteger(1, y.position), y.position)
|
|
||||||
return BinaryExpression(x, "*", yPlus1, x.position)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// X - Y*X -> X*(1 - Y)
|
|
||||||
// X - X*Y -> X*(1 - Y)
|
|
||||||
val x = expr.left
|
|
||||||
val y = determineY(x, rightBinExpr)
|
|
||||||
if(y!=null) {
|
|
||||||
val oneMinusY = BinaryExpression(NumericLiteralValue.optimalInteger(1, y.position), "-", y, y.position)
|
|
||||||
return BinaryExpression(x, "*", oneMinusY, x.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// simplify when a term is constant and determines the outcome
|
|
||||||
when (expr.operator) {
|
|
||||||
"or" -> {
|
|
||||||
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue)) {
|
|
||||||
optimizationsDone++
|
|
||||||
return constTrue
|
|
||||||
}
|
|
||||||
if (leftVal != null && !leftVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.right
|
|
||||||
}
|
|
||||||
if (rightVal != null && !rightVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"and" -> {
|
|
||||||
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue)) {
|
|
||||||
optimizationsDone++
|
|
||||||
return constFalse
|
|
||||||
}
|
|
||||||
if (leftVal != null && leftVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.right
|
|
||||||
}
|
|
||||||
if (rightVal != null && rightVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"xor" -> {
|
|
||||||
if (leftVal != null && !leftVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.right
|
|
||||||
}
|
|
||||||
if (rightVal != null && !rightVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
if (leftVal != null && leftVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return PrefixExpression("not", expr.right, expr.right.position)
|
|
||||||
}
|
|
||||||
if (rightVal != null && rightVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return PrefixExpression("not", expr.left, expr.left.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"|", "^" -> {
|
|
||||||
if (leftVal != null && !leftVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.right
|
|
||||||
}
|
|
||||||
if (rightVal != null && !rightVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"&" -> {
|
|
||||||
if (leftVal != null && !leftVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return constFalse
|
|
||||||
}
|
|
||||||
if (rightVal != null && !rightVal.asBooleanValue) {
|
|
||||||
optimizationsDone++
|
|
||||||
return constFalse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"*" -> return optimizeMultiplication(expr, leftVal, rightVal)
|
|
||||||
"/" -> return optimizeDivision(expr, leftVal, rightVal)
|
|
||||||
"+" -> return optimizeAdd(expr, leftVal, rightVal)
|
|
||||||
"-" -> return optimizeSub(expr, leftVal, rightVal)
|
|
||||||
"**" -> return optimizePower(expr, leftVal, rightVal)
|
|
||||||
"%" -> return optimizeRemainder(expr, leftVal, rightVal)
|
|
||||||
">>" -> return optimizeShiftRight(expr, rightVal)
|
|
||||||
"<<" -> return optimizeShiftLeft(expr, rightVal)
|
|
||||||
}
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
|
||||||
return when {
|
|
||||||
subBinExpr.left isSameAs x -> subBinExpr.right
|
|
||||||
subBinExpr.right isSameAs x -> subBinExpr.left
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun adjustDatatypes(expr: BinaryExpression,
|
|
||||||
leftConstVal: NumericLiteralValue?, leftDt: DataType,
|
|
||||||
rightConstVal: NumericLiteralValue?, rightDt: DataType): Boolean {
|
|
||||||
|
|
||||||
fun adjust(value: NumericLiteralValue, targetDt: DataType): Pair<Boolean, NumericLiteralValue>{
|
|
||||||
if(value.type==targetDt)
|
|
||||||
return Pair(false, value)
|
|
||||||
when(value.type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
if (targetDt == DataType.BYTE) {
|
|
||||||
if(value.number.toInt() < 127)
|
|
||||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
|
||||||
}
|
|
||||||
else if (targetDt == DataType.UWORD || targetDt == DataType.WORD)
|
|
||||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if (targetDt == DataType.UBYTE) {
|
|
||||||
if(value.number.toInt() >= 0)
|
|
||||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
|
||||||
}
|
|
||||||
else if (targetDt == DataType.UWORD) {
|
|
||||||
if(value.number.toInt() >= 0)
|
|
||||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
|
||||||
}
|
|
||||||
else if (targetDt == DataType.WORD) return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
if (targetDt == DataType.UBYTE) {
|
|
||||||
if(value.number.toInt() <= 255)
|
|
||||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
|
||||||
}
|
|
||||||
else if (targetDt == DataType.BYTE) {
|
|
||||||
if(value.number.toInt() <= 127)
|
|
||||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
|
||||||
}
|
|
||||||
else if (targetDt == DataType.WORD) {
|
|
||||||
if(value.number.toInt() <= 32767)
|
|
||||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
if (targetDt == DataType.UBYTE) {
|
|
||||||
if(value.number.toInt() in 0..255)
|
|
||||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
|
||||||
}
|
|
||||||
else if (targetDt == DataType.BYTE) {
|
|
||||||
if(value.number.toInt() in -128..127)
|
|
||||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
|
||||||
}
|
|
||||||
else if (targetDt == DataType.UWORD) {
|
|
||||||
if(value.number.toInt() >= 0)
|
|
||||||
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
return Pair(false, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(leftConstVal==null && rightConstVal!=null) {
|
|
||||||
if(leftDt largerThan rightDt) {
|
|
||||||
val (adjusted, newValue) = adjust(rightConstVal, leftDt)
|
|
||||||
if (adjusted) {
|
|
||||||
expr.right = newValue
|
|
||||||
optimizationsDone++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
} else if(leftConstVal!=null && rightConstVal==null) {
|
|
||||||
if(rightDt largerThan leftDt) {
|
|
||||||
val (adjusted, newValue) = adjust(leftConstVal, rightDt)
|
|
||||||
if (adjusted) {
|
|
||||||
expr.left = newValue
|
|
||||||
optimizationsDone++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return false // two const values, don't adjust (should have been const-folded away)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
|
|
||||||
|
|
||||||
private fun reorderAssociative(expr: BinaryExpression, leftVal: NumericLiteralValue?): ReorderedAssociativeBinaryExpr {
|
|
||||||
if(expr.operator in associativeOperators && leftVal!=null) {
|
|
||||||
// swap left and right so that right is always the constant
|
|
||||||
val tmp = expr.left
|
|
||||||
expr.left = expr.right
|
|
||||||
expr.right = tmp
|
|
||||||
optimizationsDone++
|
|
||||||
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(program), leftVal)
|
|
||||||
}
|
|
||||||
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: NumericLiteralValue?, prightVal: NumericLiteralValue?): Expression {
|
|
||||||
if(pleftVal==null && prightVal==null)
|
|
||||||
return pexpr
|
|
||||||
|
|
||||||
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
|
|
||||||
if(rightVal!=null) {
|
|
||||||
// right value is a constant, see if we can optimize
|
|
||||||
val rightConst: NumericLiteralValue = rightVal
|
|
||||||
when(rightConst.number.toDouble()) {
|
|
||||||
0.0 -> {
|
|
||||||
// left
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// no need to check for left val constant (because of associativity)
|
|
||||||
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
|
|
||||||
if(leftVal==null && rightVal==null)
|
|
||||||
return expr
|
|
||||||
|
|
||||||
if(rightVal!=null) {
|
|
||||||
// right value is a constant, see if we can optimize
|
|
||||||
val rightConst: NumericLiteralValue = rightVal
|
|
||||||
when(rightConst.number.toDouble()) {
|
|
||||||
0.0 -> {
|
|
||||||
// left
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(leftVal!=null) {
|
|
||||||
// left value is a constant, see if we can optimize
|
|
||||||
when(leftVal.number.toDouble()) {
|
|
||||||
0.0 -> {
|
|
||||||
// -right
|
|
||||||
optimizationsDone++
|
|
||||||
return PrefixExpression("-", expr.right, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
|
|
||||||
if(leftVal==null && rightVal==null)
|
|
||||||
return expr
|
|
||||||
|
|
||||||
if(rightVal!=null) {
|
|
||||||
// right value is a constant, see if we can optimize
|
|
||||||
val rightConst: NumericLiteralValue = rightVal
|
|
||||||
when(rightConst.number.toDouble()) {
|
|
||||||
-3.0 -> {
|
|
||||||
// -1/(left*left*left)
|
|
||||||
optimizationsDone++
|
|
||||||
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
|
||||||
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
|
|
||||||
expr.position)
|
|
||||||
}
|
|
||||||
-2.0 -> {
|
|
||||||
// -1/(left*left)
|
|
||||||
optimizationsDone++
|
|
||||||
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
|
||||||
BinaryExpression(expr.left, "*", expr.left, expr.position),
|
|
||||||
expr.position)
|
|
||||||
}
|
|
||||||
-1.0 -> {
|
|
||||||
// -1/left
|
|
||||||
optimizationsDone++
|
|
||||||
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
|
|
||||||
expr.left, expr.position)
|
|
||||||
}
|
|
||||||
0.0 -> {
|
|
||||||
// 1
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue(rightConst.type, 1, expr.position)
|
|
||||||
}
|
|
||||||
0.5 -> {
|
|
||||||
// sqrt(left)
|
|
||||||
optimizationsDone++
|
|
||||||
return FunctionCall(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
|
|
||||||
}
|
|
||||||
1.0 -> {
|
|
||||||
// left
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
2.0 -> {
|
|
||||||
// left*left
|
|
||||||
optimizationsDone++
|
|
||||||
return BinaryExpression(expr.left, "*", expr.left, expr.position)
|
|
||||||
}
|
|
||||||
3.0 -> {
|
|
||||||
// left*left*left
|
|
||||||
optimizationsDone++
|
|
||||||
return BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(leftVal!=null) {
|
|
||||||
// left value is a constant, see if we can optimize
|
|
||||||
when(leftVal.number.toDouble()) {
|
|
||||||
-1.0 -> {
|
|
||||||
// -1
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
|
|
||||||
}
|
|
||||||
0.0 -> {
|
|
||||||
// 0
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
|
||||||
}
|
|
||||||
1.0 -> {
|
|
||||||
//1
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue(leftVal.type, 1, expr.position)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
|
|
||||||
if(leftVal==null && rightVal==null)
|
|
||||||
return expr
|
|
||||||
|
|
||||||
// simplify assignments A = B <operator> C
|
|
||||||
|
|
||||||
val cv = rightVal?.number?.toInt()?.toDouble()
|
|
||||||
when(expr.operator) {
|
|
||||||
"%" -> {
|
|
||||||
if (cv == 1.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
|
|
||||||
} else if (cv == 2.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
expr.operator = "&"
|
|
||||||
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private val powersOfTwo = (1 .. 16).map { (2.0).pow(it) }.toSet()
|
|
||||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
|
||||||
|
|
||||||
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
|
|
||||||
if(leftVal==null && rightVal==null)
|
|
||||||
return expr
|
|
||||||
|
|
||||||
// cannot shuffle assiciativity with division!
|
|
||||||
if(rightVal!=null) {
|
|
||||||
// right value is a constant, see if we can optimize
|
|
||||||
val rightConst: NumericLiteralValue = rightVal
|
|
||||||
val cv = rightConst.number.toDouble()
|
|
||||||
val leftIDt = expr.left.inferType(program)
|
|
||||||
if(!leftIDt.isKnown)
|
|
||||||
return expr
|
|
||||||
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
|
||||||
when(cv) {
|
|
||||||
-1.0 -> {
|
|
||||||
// '/' -> -left
|
|
||||||
if (expr.operator == "/") {
|
|
||||||
optimizationsDone++
|
|
||||||
return PrefixExpression("-", expr.left, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1.0 -> {
|
|
||||||
// '/' -> left
|
|
||||||
if (expr.operator == "/") {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in powersOfTwo -> {
|
|
||||||
if(leftDt in IntegerDatatypes) {
|
|
||||||
// divided by a power of two => shift right
|
|
||||||
optimizationsDone++
|
|
||||||
val numshifts = log2(cv).toInt()
|
|
||||||
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in negativePowersOfTwo -> {
|
|
||||||
if(leftDt in IntegerDatatypes) {
|
|
||||||
// divided by a negative power of two => negate, then shift right
|
|
||||||
optimizationsDone++
|
|
||||||
val numshifts = log2(-cv).toInt()
|
|
||||||
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (leftDt == DataType.UBYTE) {
|
|
||||||
if(abs(rightConst.number.toDouble()) >= 256.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (leftDt == DataType.UWORD) {
|
|
||||||
if(abs(rightConst.number.toDouble()) >= 65536.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(leftVal!=null) {
|
|
||||||
// left value is a constant, see if we can optimize
|
|
||||||
when(leftVal.number.toDouble()) {
|
|
||||||
0.0 -> {
|
|
||||||
// 0
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue(leftVal.type, 0, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeMultiplication(pexpr: BinaryExpression, pleftVal: NumericLiteralValue?, prightVal: NumericLiteralValue?): Expression {
|
|
||||||
if(pleftVal==null && prightVal==null)
|
|
||||||
return pexpr
|
|
||||||
|
|
||||||
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
|
|
||||||
if(rightVal!=null) {
|
|
||||||
// right value is a constant, see if we can optimize
|
|
||||||
val leftValue: Expression = expr.left
|
|
||||||
val rightConst: NumericLiteralValue = rightVal
|
|
||||||
when(val cv = rightConst.number.toDouble()) {
|
|
||||||
-1.0 -> {
|
|
||||||
// -left
|
|
||||||
optimizationsDone++
|
|
||||||
return PrefixExpression("-", leftValue, expr.position)
|
|
||||||
}
|
|
||||||
0.0 -> {
|
|
||||||
// 0
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue(rightConst.type, 0, expr.position)
|
|
||||||
}
|
|
||||||
1.0 -> {
|
|
||||||
// left
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
in powersOfTwo -> {
|
|
||||||
if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
|
|
||||||
// times a power of two => shift left
|
|
||||||
optimizationsDone++
|
|
||||||
val numshifts = log2(cv).toInt()
|
|
||||||
return BinaryExpression(expr.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
in negativePowersOfTwo -> {
|
|
||||||
if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
|
|
||||||
// times a negative power of two => negate, then shift left
|
|
||||||
optimizationsDone++
|
|
||||||
val numshifts = log2(-cv).toInt()
|
|
||||||
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// no need to check for left val constant (because of associativity)
|
|
||||||
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
|
|
||||||
if(amountLv==null)
|
|
||||||
return expr
|
|
||||||
|
|
||||||
val amount=amountLv.number.toInt()
|
|
||||||
if(amount==0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
|
|
||||||
when(targetDt) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
|
||||||
if(amount>=8) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD, DataType.WORD -> {
|
|
||||||
if(amount>=16) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
|
||||||
}
|
|
||||||
else if(amount>=8) {
|
|
||||||
optimizationsDone++
|
|
||||||
val lsb=TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
|
|
||||||
if(amount==8) {
|
|
||||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position)
|
|
||||||
}
|
|
||||||
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
|
|
||||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
|
|
||||||
if(amountLv==null)
|
|
||||||
return expr
|
|
||||||
|
|
||||||
val amount=amountLv.number.toInt()
|
|
||||||
if(amount==0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return expr.left
|
|
||||||
}
|
|
||||||
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
|
|
||||||
when(targetDt) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
if(amount>=8) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if(amount>8) {
|
|
||||||
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position)
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
if(amount>=16) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
|
||||||
}
|
|
||||||
else if(amount>=8) {
|
|
||||||
optimizationsDone++
|
|
||||||
val msb=FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
|
|
||||||
if(amount==8)
|
|
||||||
return msb
|
|
||||||
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
if(amount>16) {
|
|
||||||
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
|
|
||||||
return expr
|
|
||||||
} else if(amount>=8) {
|
|
||||||
optimizationsDone++
|
|
||||||
val msbAsByte = TypecastExpression(
|
|
||||||
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
|
|
||||||
DataType.BYTE,
|
|
||||||
true, expr.position)
|
|
||||||
if(amount==8)
|
|
||||||
return msbAsByte
|
|
||||||
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,12 @@
|
|||||||
package prog8.optimizer
|
package prog8.optimizer
|
||||||
|
|
||||||
import prog8.ast.INameScope
|
import prog8.ast.INameScope
|
||||||
import prog8.ast.Module
|
|
||||||
import prog8.ast.Node
|
import prog8.ast.Node
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.base.*
|
import prog8.ast.base.*
|
||||||
import prog8.ast.expressions.*
|
import prog8.ast.expressions.*
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstModification
|
||||||
import prog8.ast.processing.IAstVisitor
|
import prog8.ast.processing.IAstVisitor
|
||||||
import prog8.ast.statements.*
|
import prog8.ast.statements.*
|
||||||
import prog8.compiler.target.CompilationTarget
|
import prog8.compiler.target.CompilationTarget
|
||||||
@ -15,93 +15,42 @@ import kotlin.math.floor
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO: remove unreachable code?
|
TODO: remove unreachable code after return and exit()
|
||||||
TODO: proper inlining of tiny subroutines (correctly renaming/relocating all variables in them and refs to those as well, or restrict to subs without variables?)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
internal class StatementOptimizer(private val program: Program) : IAstModifyingVisitor {
|
internal class StatementOptimizer(private val program: Program,
|
||||||
var optimizationsDone: Int = 0
|
private val errors: ErrorReporter) : AstWalker() {
|
||||||
private set
|
|
||||||
|
|
||||||
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
private val noModifications = emptyList<IAstModification>()
|
||||||
private val callgraph = CallGraph(program)
|
private val callgraph = CallGraph(program)
|
||||||
private val vardeclsToRemove = mutableListOf<VarDecl>()
|
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||||
removeUnusedCode(callgraph)
|
|
||||||
super.visit(program)
|
|
||||||
|
|
||||||
for(decl in vardeclsToRemove) {
|
|
||||||
decl.definingScope().remove(decl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeUnusedCode(callgraph: CallGraph) {
|
|
||||||
// remove all subroutines that aren't called, or are empty
|
|
||||||
val removeSubroutines = mutableSetOf<Subroutine>()
|
|
||||||
val entrypoint = program.entrypoint()
|
|
||||||
program.modules.forEach {
|
|
||||||
callgraph.forAllSubroutines(it) { sub ->
|
|
||||||
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
|
||||||
removeSubroutines.add(sub)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removeSubroutines.isNotEmpty()) {
|
|
||||||
removeSubroutines.forEach {
|
|
||||||
it.definingScope().remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val removeBlocks = mutableSetOf<Block>()
|
|
||||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
|
||||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
|
||||||
removeBlocks.add(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removeBlocks.isNotEmpty()) {
|
|
||||||
removeBlocks.forEach { it.definingScope().remove(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
|
||||||
val removeModules = mutableSetOf<Module>()
|
|
||||||
program.modules.forEach {
|
|
||||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
|
||||||
removeModules.add(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (removeModules.isNotEmpty()) {
|
|
||||||
program.modules.removeAll(removeModules)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(block: Block): Statement {
|
|
||||||
if("force_output" !in block.options()) {
|
if("force_output" !in block.options()) {
|
||||||
if (block.containsNoCodeNorVars()) {
|
if (block.containsNoCodeNorVars()) {
|
||||||
optimizationsDone++
|
errors.warn("removing empty block '${block.name}'", block.position)
|
||||||
printWarning("removing empty block '${block.name}'", block.position)
|
return listOf(IAstModification.Remove(block, parent))
|
||||||
return NopStatement.insteadOf(block)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block !in callgraph.usedSymbols) {
|
if (block !in callgraph.usedSymbols) {
|
||||||
optimizationsDone++
|
errors.warn("removing unused block '${block.name}'", block.position)
|
||||||
printWarning("removing unused block '${block.name}'", block.position)
|
return listOf(IAstModification.Remove(block, parent))
|
||||||
return NopStatement.insteadOf(block) // remove unused block
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
return super.visit(block)
|
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(subroutine: Subroutine): Statement {
|
|
||||||
super.visit(subroutine)
|
|
||||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
||||||
if(subroutine.asmAddress==null && !forceOutput) {
|
if(subroutine.asmAddress==null && !forceOutput) {
|
||||||
if(subroutine.containsNoCodeNorVars()) {
|
if(subroutine.containsNoCodeNorVars()) {
|
||||||
printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||||
optimizationsDone++
|
val removals = callgraph.calledBy.getValue(subroutine).map {
|
||||||
return NopStatement.insteadOf(subroutine)
|
IAstModification.Remove(it, it.parent)
|
||||||
|
}.toMutableList()
|
||||||
|
removals += IAstModification.Remove(subroutine, parent)
|
||||||
|
return removals
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,24 +60,360 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||||
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||||
optimizationsDone++
|
return listOf(IAstModification.Remove(subroutine, parent))
|
||||||
return NopStatement.insteadOf(subroutine)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return subroutine
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): Statement {
|
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||||
|
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||||
|
return linesToRemove.reversed().map { IAstModification.Remove(scope.statements[it], scope) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
||||||
if(decl.type == VarDeclType.VAR)
|
if(decl.type == VarDeclType.VAR)
|
||||||
printWarning("removing unused variable ${decl.type} '${decl.name}'", decl.position)
|
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(decl)
|
return listOf(IAstModification.Remove(decl, parent))
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.visit(decl)
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
||||||
|
val functionName = functionCallStatement.target.nameInSource[0]
|
||||||
|
if (functionName in pureBuiltinFunctions) {
|
||||||
|
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||||
|
return listOf(IAstModification.Remove(functionCallStatement, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")) {
|
||||||
|
val arg = functionCallStatement.args.single()
|
||||||
|
val stringVar: IdentifierReference?
|
||||||
|
stringVar = if(arg is AddressOf) {
|
||||||
|
arg.identifier
|
||||||
|
} else {
|
||||||
|
arg as? IdentifierReference
|
||||||
|
}
|
||||||
|
if(stringVar!=null) {
|
||||||
|
val vardecl = stringVar.targetVarDecl(program.namespace)!!
|
||||||
|
val string = vardecl.value!! as StringLiteralValue
|
||||||
|
val pos = functionCallStatement.position
|
||||||
|
if(string.value.length==1) {
|
||||||
|
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
|
||||||
|
val chrout = FunctionCallStatement(
|
||||||
|
IdentifierReference(listOf("c64", "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 = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||||
|
val chrout1 = FunctionCallStatement(
|
||||||
|
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||||
|
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
||||||
|
functionCallStatement.void, pos
|
||||||
|
)
|
||||||
|
val chrout2 = FunctionCallStatement(
|
||||||
|
IdentifierReference(listOf("c64", "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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
|
||||||
|
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||||
|
if(subroutine!=null) {
|
||||||
|
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||||
|
if(first is ReturnFromIrq || first is Return)
|
||||||
|
return listOf(IAstModification.Remove(functionCallStatement, parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||||
|
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
|
||||||
|
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||||
|
if(subroutine!=null) {
|
||||||
|
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||||
|
if(first is Return && first.value!=null) {
|
||||||
|
val constval = first.value?.constValue(program)
|
||||||
|
if(constval!=null)
|
||||||
|
return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||||
|
// remove empty if statements
|
||||||
|
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
|
||||||
|
return listOf(IAstModification.Remove(ifStatement, parent))
|
||||||
|
|
||||||
|
// empty true part? switch with the else part
|
||||||
|
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
||||||
|
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
||||||
|
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||||
|
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
|
||||||
|
return listOf(
|
||||||
|
IAstModification.ReplaceNode(ifStatement.condition, invertedCondition, ifStatement),
|
||||||
|
IAstModification.ReplaceNode(ifStatement.truepart, truepart, ifStatement),
|
||||||
|
IAstModification.ReplaceNode(ifStatement.elsepart, emptyscope, ifStatement)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val constvalue = ifStatement.condition.constValue(program)
|
||||||
|
if(constvalue!=null) {
|
||||||
|
return if(constvalue.asBooleanValue){
|
||||||
|
// always true -> keep only if-part
|
||||||
|
errors.warn("condition is always true", ifStatement.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.truepart, parent))
|
||||||
|
} else {
|
||||||
|
// always false -> keep only else-part
|
||||||
|
errors.warn("condition is always false", ifStatement.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.elsepart, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(forLoop.body.containsNoCodeNorVars()) {
|
||||||
|
errors.warn("removing empty for loop", forLoop.position)
|
||||||
|
return listOf(IAstModification.Remove(forLoop, parent))
|
||||||
|
} else if(forLoop.body.statements.size==1) {
|
||||||
|
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||||
|
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
||||||
|
// remove empty for loop (only loopvar decl in it)
|
||||||
|
return listOf(IAstModification.Remove(forLoop, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val range = forLoop.iterable as? RangeExpr
|
||||||
|
if(range!=null) {
|
||||||
|
if(range.size()==1) {
|
||||||
|
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||||
|
// loopvar/reg = range value , follow by block
|
||||||
|
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||||
|
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position))
|
||||||
|
scope.statements.addAll(forLoop.body.statements)
|
||||||
|
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||||
|
if(iterable!=null) {
|
||||||
|
if(iterable.datatype==DataType.STR) {
|
||||||
|
val sv = iterable.value as StringLiteralValue
|
||||||
|
val size = sv.value.length
|
||||||
|
if(size==1) {
|
||||||
|
// loop over string of length 1 -> just assign the single character
|
||||||
|
val character = CompilationTarget.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), null, byte, forLoop.position))
|
||||||
|
scope.statements.addAll(forLoop.body.statements)
|
||||||
|
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(iterable.datatype in ArrayDatatypes) {
|
||||||
|
val size = iterable.arraysize!!.size()
|
||||||
|
if(size==1) {
|
||||||
|
// loop over array of length 1 -> just assign the single value
|
||||||
|
val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number
|
||||||
|
if(av!=null) {
|
||||||
|
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||||
|
scope.statements.add(Assignment(
|
||||||
|
AssignTarget(forLoop.loopVar, null, null, forLoop.position), null, NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
|
||||||
|
forLoop.position))
|
||||||
|
scope.statements.addAll(forLoop.body.statements)
|
||||||
|
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
val constvalue = untilLoop.untilCondition.constValue(program)
|
||||||
|
if(constvalue!=null) {
|
||||||
|
if(constvalue.asBooleanValue) {
|
||||||
|
// always true -> keep only the statement block (if there are no continue and break statements)
|
||||||
|
errors.warn("condition is always true", untilLoop.untilCondition.position)
|
||||||
|
if(!hasContinueOrBreak(untilLoop.body))
|
||||||
|
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
|
||||||
|
} else {
|
||||||
|
// always false
|
||||||
|
val forever = RepeatLoop(null, untilLoop.body, untilLoop.position)
|
||||||
|
return listOf(IAstModification.ReplaceNode(untilLoop, forever, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
val constvalue = whileLoop.condition.constValue(program)
|
||||||
|
if(constvalue!=null) {
|
||||||
|
return if(constvalue.asBooleanValue) {
|
||||||
|
// always true
|
||||||
|
val forever = RepeatLoop(null, whileLoop.body, whileLoop.position)
|
||||||
|
listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
|
||||||
|
} else {
|
||||||
|
// always false -> remove the while statement altogether
|
||||||
|
errors.warn("condition is always false", whileLoop.condition.position)
|
||||||
|
listOf(IAstModification.Remove(whileLoop, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||||
|
val iter = repeatLoop.iterations
|
||||||
|
if(iter!=null) {
|
||||||
|
if(repeatLoop.body.containsNoCodeNorVars()) {
|
||||||
|
errors.warn("empty loop removed", repeatLoop.position)
|
||||||
|
return listOf(IAstModification.Remove(repeatLoop, parent))
|
||||||
|
}
|
||||||
|
val iterations = iter.constValue(program)?.number?.toInt()
|
||||||
|
if (iterations == 0) {
|
||||||
|
errors.warn("iterations is always 0, removed loop", iter.position)
|
||||||
|
return listOf(IAstModification.Remove(repeatLoop, parent))
|
||||||
|
}
|
||||||
|
if (iterations == 1)
|
||||||
|
errors.warn("iterations is always 1", iter.position)
|
||||||
|
}
|
||||||
|
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()
|
||||||
|
val label = jump.identifier?.targetStatement(scope)
|
||||||
|
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
||||||
|
return listOf(IAstModification.Remove(jump, parent))
|
||||||
|
|
||||||
|
return noModifications
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||||
|
if(assignment.aug_op!=null)
|
||||||
|
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||||
|
|
||||||
|
// remove assignments to self
|
||||||
|
if(assignment.target isSameAs assignment.value) {
|
||||||
|
if(assignment.target.isNotMemory(program.namespace))
|
||||||
|
return listOf(IAstModification.Remove(assignment, parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
val targetIDt = assignment.target.inferType(program, assignment)
|
||||||
|
if(!targetIDt.isKnown)
|
||||||
|
throw FatalAstException("can't infer type of assignment target")
|
||||||
|
|
||||||
|
|
||||||
|
// optimize binary expressions a bit
|
||||||
|
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
||||||
|
val bexpr=assignment.value as? BinaryExpression
|
||||||
|
if(bexpr!=null) {
|
||||||
|
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||||
|
if (cv != null && assignment.target isSameAs bexpr.left) {
|
||||||
|
// assignments of the form: X = X <operator> <expr>
|
||||||
|
// remove assignments that have no effect (such as X=X+0)
|
||||||
|
// optimize/rewrite some other expressions
|
||||||
|
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
|
||||||
|
when (bexpr.operator) {
|
||||||
|
"+" -> {
|
||||||
|
if (cv == 0.0) {
|
||||||
|
return listOf(IAstModification.Remove(assignment, parent))
|
||||||
|
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||||
|
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
||||||
|
// replace by several INCs (a bit less when dealing with memory targets)
|
||||||
|
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
||||||
|
repeat(cv.toInt()) {
|
||||||
|
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
||||||
|
}
|
||||||
|
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"-" -> {
|
||||||
|
if (cv == 0.0) {
|
||||||
|
return listOf(IAstModification.Remove(assignment, parent))
|
||||||
|
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||||
|
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
||||||
|
// replace by several DECs (a bit less when dealing with memory targets)
|
||||||
|
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||||
|
repeat(cv.toInt()) {
|
||||||
|
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
||||||
|
}
|
||||||
|
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"*" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||||
|
"/" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||||
|
"**" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||||
|
"|" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||||
|
"^" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||||
|
"<<" -> {
|
||||||
|
if (cv == 0.0)
|
||||||
|
return listOf(IAstModification.Remove(assignment, parent))
|
||||||
|
// replace by in-place lsl(...) call
|
||||||
|
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
||||||
|
var numshifts = cv.toInt()
|
||||||
|
while (numshifts > 0) {
|
||||||
|
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
|
||||||
|
mutableListOf(bexpr.left), true, assignment.position))
|
||||||
|
numshifts--
|
||||||
|
}
|
||||||
|
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
|
||||||
|
}
|
||||||
|
">>" -> {
|
||||||
|
if (cv == 0.0)
|
||||||
|
return listOf(IAstModification.Remove(assignment, parent))
|
||||||
|
// replace by in-place lsr(...) call
|
||||||
|
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
||||||
|
var numshifts = cv.toInt()
|
||||||
|
while (numshifts > 0) {
|
||||||
|
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
|
||||||
|
mutableListOf(bexpr.left), true, assignment.position))
|
||||||
|
numshifts--
|
||||||
|
}
|
||||||
|
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return noModifications
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
|
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
|
||||||
@ -156,239 +441,21 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
|||||||
return linesToRemove
|
return linesToRemove
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
|
||||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
|
||||||
val functionName = functionCallStatement.target.nameInSource[0]
|
|
||||||
if (functionName in pureBuiltinFunctions) {
|
|
||||||
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(functionCallStatement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
|
|
||||||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
|
|
||||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
|
||||||
val arg = functionCallStatement.args.single()
|
|
||||||
val stringVar: IdentifierReference?
|
|
||||||
stringVar = if(arg is AddressOf) {
|
|
||||||
arg.identifier
|
|
||||||
} else {
|
|
||||||
arg as? IdentifierReference
|
|
||||||
}
|
|
||||||
if(stringVar!=null) {
|
|
||||||
val vardecl = stringVar.targetVarDecl(program.namespace)!!
|
|
||||||
val string = vardecl.value!! as StringLiteralValue
|
|
||||||
if(string.value.length==1) {
|
|
||||||
val firstCharEncoded = CompilationTarget.encodeString(string.value)[0]
|
|
||||||
functionCallStatement.args.clear()
|
|
||||||
functionCallStatement.args.add(NumericLiteralValue.optimalInteger(firstCharEncoded.toInt(), functionCallStatement.position))
|
|
||||||
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
|
|
||||||
vardeclsToRemove.add(vardecl)
|
|
||||||
optimizationsDone++
|
|
||||||
return functionCallStatement
|
|
||||||
} else if(string.value.length==2) {
|
|
||||||
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2))
|
|
||||||
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
|
|
||||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
|
||||||
mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[0].toInt(), functionCallStatement.position)),
|
|
||||||
functionCallStatement.void, functionCallStatement.position))
|
|
||||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
|
||||||
mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[1].toInt(), functionCallStatement.position)),
|
|
||||||
functionCallStatement.void, functionCallStatement.position))
|
|
||||||
vardeclsToRemove.add(vardecl)
|
|
||||||
optimizationsDone++
|
|
||||||
return scope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if it calls a subroutine,
|
|
||||||
// and the first instruction in the subroutine is a jump, call that jump target instead
|
|
||||||
// if the first instruction in the subroutine is a return statement, replace with a nop instruction
|
|
||||||
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
|
|
||||||
if(subroutine!=null) {
|
|
||||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
|
||||||
if(first is Jump && first.identifier!=null) {
|
|
||||||
optimizationsDone++
|
|
||||||
return FunctionCallStatement(first.identifier, functionCallStatement.args, functionCallStatement.void, functionCallStatement.position)
|
|
||||||
}
|
|
||||||
if(first is ReturnFromIrq || first is Return) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(functionCallStatement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.visit(functionCallStatement)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(functionCall: FunctionCall): Expression {
|
|
||||||
// if it calls a subroutine,
|
|
||||||
// and the first instruction in the subroutine is a jump, call that jump target instead
|
|
||||||
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
|
|
||||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
|
||||||
if(subroutine!=null) {
|
|
||||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
|
||||||
if(first is Jump && first.identifier!=null) {
|
|
||||||
optimizationsDone++
|
|
||||||
return FunctionCall(first.identifier, functionCall.args, functionCall.position)
|
|
||||||
}
|
|
||||||
if(first is Return && first.value!=null) {
|
|
||||||
val constval = first.value?.constValue(program)
|
|
||||||
if(constval!=null)
|
|
||||||
return constval
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(functionCall)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(ifStatement: IfStatement): Statement {
|
|
||||||
super.visit(ifStatement)
|
|
||||||
|
|
||||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(ifStatement)
|
|
||||||
}
|
|
||||||
|
|
||||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
|
||||||
// invert the condition and move else part to true part
|
|
||||||
ifStatement.truepart = ifStatement.elsepart
|
|
||||||
ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
|
||||||
ifStatement.condition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
|
||||||
optimizationsDone++
|
|
||||||
return ifStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
val constvalue = ifStatement.condition.constValue(program)
|
|
||||||
if(constvalue!=null) {
|
|
||||||
return if(constvalue.asBooleanValue){
|
|
||||||
// always true -> keep only if-part
|
|
||||||
printWarning("condition is always true", ifStatement.position) // TODO don't warn this if the condition is just the single value 'true'
|
|
||||||
optimizationsDone++
|
|
||||||
ifStatement.truepart
|
|
||||||
} else {
|
|
||||||
// always false -> keep only else-part
|
|
||||||
printWarning("condition is always false", ifStatement.position)
|
|
||||||
optimizationsDone++
|
|
||||||
ifStatement.elsepart
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ifStatement
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(forLoop: ForLoop): Statement {
|
|
||||||
super.visit(forLoop)
|
|
||||||
if(forLoop.body.containsNoCodeNorVars()) {
|
|
||||||
// remove empty for loop
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(forLoop)
|
|
||||||
} else if(forLoop.body.statements.size==1) {
|
|
||||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
|
||||||
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
|
|
||||||
// remove empty for loop
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(forLoop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val range = forLoop.iterable as? RangeExpr
|
|
||||||
if(range!=null) {
|
|
||||||
if(range.size()==1) {
|
|
||||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
|
||||||
// loopvar/reg = range value , follow by block
|
|
||||||
val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position)
|
|
||||||
forLoop.body.statements.add(0, assignment)
|
|
||||||
optimizationsDone++
|
|
||||||
return forLoop.body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return forLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(whileLoop: WhileLoop): Statement {
|
|
||||||
super.visit(whileLoop)
|
|
||||||
val constvalue = whileLoop.condition.constValue(program)
|
|
||||||
if(constvalue!=null) {
|
|
||||||
return if(constvalue.asBooleanValue){
|
|
||||||
// always true -> print a warning, and optimize into body + jump (if there are no continue and break statements)
|
|
||||||
printWarning("condition is always true", whileLoop.condition.position)
|
|
||||||
if(hasContinueOrBreak(whileLoop.body))
|
|
||||||
return whileLoop
|
|
||||||
val backLabelName = "_prog8_back${whileLoop.position.line}"
|
|
||||||
val label = Label(backLabelName, whileLoop.condition.position)
|
|
||||||
whileLoop.body.statements.add(0, label)
|
|
||||||
whileLoop.body.statements.add(Jump(null,
|
|
||||||
IdentifierReference(listOf(backLabelName), whileLoop.condition.position),
|
|
||||||
null, whileLoop.condition.position))
|
|
||||||
optimizationsDone++
|
|
||||||
return whileLoop.body
|
|
||||||
} else {
|
|
||||||
// always false -> ditch whole statement
|
|
||||||
printWarning("condition is always false", whileLoop.condition.position)
|
|
||||||
optimizationsDone++
|
|
||||||
NopStatement.insteadOf(whileLoop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return whileLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(repeatLoop: RepeatLoop): Statement {
|
|
||||||
super.visit(repeatLoop)
|
|
||||||
val constvalue = repeatLoop.untilCondition.constValue(program)
|
|
||||||
if(constvalue!=null) {
|
|
||||||
return if(constvalue.asBooleanValue){
|
|
||||||
// always true -> keep only the statement block (if there are no continue and break statements)
|
|
||||||
printWarning("condition is always true", repeatLoop.untilCondition.position)
|
|
||||||
if(hasContinueOrBreak(repeatLoop.body))
|
|
||||||
repeatLoop
|
|
||||||
else {
|
|
||||||
optimizationsDone++
|
|
||||||
repeatLoop.body
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// always false -> print a warning, and optimize into body + jump (if there are no continue and break statements)
|
|
||||||
printWarning("condition is always false", repeatLoop.untilCondition.position)
|
|
||||||
if(hasContinueOrBreak(repeatLoop.body))
|
|
||||||
return repeatLoop
|
|
||||||
val backLabelName = "_prog8_back${repeatLoop.position.line}"
|
|
||||||
val label = Label(backLabelName, repeatLoop.untilCondition.position)
|
|
||||||
repeatLoop.body.statements.add(0, label)
|
|
||||||
repeatLoop.body.statements.add(Jump(null,
|
|
||||||
IdentifierReference(listOf(backLabelName), repeatLoop.untilCondition.position),
|
|
||||||
null, repeatLoop.untilCondition.position))
|
|
||||||
optimizationsDone++
|
|
||||||
return repeatLoop.body
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return repeatLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(whenStatement: WhenStatement): Statement {
|
|
||||||
val choices = whenStatement.choices.toList()
|
|
||||||
for(choice in choices) {
|
|
||||||
if(choice.statements.containsNoCodeNorVars())
|
|
||||||
whenStatement.choices.remove(choice)
|
|
||||||
}
|
|
||||||
return super.visit(whenStatement)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hasContinueOrBreak(scope: INameScope): Boolean {
|
private fun hasContinueOrBreak(scope: INameScope): Boolean {
|
||||||
|
|
||||||
class Searcher: IAstModifyingVisitor
|
class Searcher: IAstVisitor
|
||||||
{
|
{
|
||||||
var count=0
|
var count=0
|
||||||
|
|
||||||
override fun visit(breakStmt: Break): Statement {
|
override fun visit(breakStmt: Break) {
|
||||||
count++
|
count++
|
||||||
return super.visit(breakStmt)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(contStmt: Continue): Statement {
|
override fun visit(contStmt: Continue) {
|
||||||
count++
|
count++
|
||||||
return super.visit(contStmt)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val s=Searcher()
|
val s=Searcher()
|
||||||
for(stmt in scope.statements) {
|
for(stmt in scope.statements) {
|
||||||
stmt.accept(s)
|
stmt.accept(s)
|
||||||
@ -398,221 +465,4 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
|
|||||||
return s.count > 0
|
return s.count > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visit(jump: Jump): Statement {
|
|
||||||
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
|
|
||||||
if(subroutine!=null) {
|
|
||||||
// if the first instruction in the subroutine is another jump, shortcut this one
|
|
||||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
|
||||||
if(first is Jump) {
|
|
||||||
optimizationsDone++
|
|
||||||
return first
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the jump is to the next statement, remove the jump
|
|
||||||
val scope = jump.definingScope()
|
|
||||||
val label = jump.identifier?.targetStatement(scope)
|
|
||||||
if(label!=null) {
|
|
||||||
if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(jump)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return jump
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(assignment: Assignment): Statement {
|
|
||||||
if(assignment.aug_op!=null)
|
|
||||||
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
|
|
||||||
|
|
||||||
if(assignment.target isSameAs assignment.value) {
|
|
||||||
if(assignment.target.isNotMemory(program.namespace)) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(assignment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val targetIDt = assignment.target.inferType(program, assignment)
|
|
||||||
if(!targetIDt.isKnown)
|
|
||||||
throw FatalAstException("can't infer type of assignment target")
|
|
||||||
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
|
||||||
val bexpr=assignment.value as? BinaryExpression
|
|
||||||
if(bexpr!=null) {
|
|
||||||
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
|
||||||
if (cv == null) {
|
|
||||||
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
|
|
||||||
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
|
|
||||||
bexpr.operator = "*"
|
|
||||||
bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
|
|
||||||
optimizationsDone++
|
|
||||||
return assignment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (assignment.target isSameAs bexpr.left) {
|
|
||||||
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
|
|
||||||
// A = A <operator> B
|
|
||||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
|
|
||||||
|
|
||||||
when (bexpr.operator) {
|
|
||||||
"+" -> {
|
|
||||||
if (cv == 0.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(assignment)
|
|
||||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
|
||||||
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
|
||||||
// replace by several INCs (a bit less when dealing with memory targets)
|
|
||||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
|
||||||
repeat(cv.toInt()) {
|
|
||||||
decs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
|
||||||
}
|
|
||||||
return decs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"-" -> {
|
|
||||||
if (cv == 0.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(assignment)
|
|
||||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
|
||||||
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
|
||||||
// replace by several DECs (a bit less when dealing with memory targets)
|
|
||||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
|
||||||
repeat(cv.toInt()) {
|
|
||||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
|
||||||
}
|
|
||||||
return decs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"*" -> if (cv == 1.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(assignment)
|
|
||||||
}
|
|
||||||
"/" -> if (cv == 1.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(assignment)
|
|
||||||
}
|
|
||||||
"**" -> if (cv == 1.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(assignment)
|
|
||||||
}
|
|
||||||
"|" -> if (cv == 0.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(assignment)
|
|
||||||
}
|
|
||||||
"^" -> if (cv == 0.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(assignment)
|
|
||||||
}
|
|
||||||
"<<" -> {
|
|
||||||
if (cv == 0.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(assignment)
|
|
||||||
}
|
|
||||||
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
|
|
||||||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
|
|
||||||
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
|
|
||||||
assignment.value.linkParents(assignment)
|
|
||||||
optimizationsDone++
|
|
||||||
} else {
|
|
||||||
// replace by in-place lsl(...) call
|
|
||||||
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
|
||||||
var numshifts = cv.toInt()
|
|
||||||
while (numshifts > 0) {
|
|
||||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
|
|
||||||
mutableListOf(bexpr.left), true, assignment.position))
|
|
||||||
numshifts--
|
|
||||||
}
|
|
||||||
optimizationsDone++
|
|
||||||
return scope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
">>" -> {
|
|
||||||
if (cv == 0.0) {
|
|
||||||
optimizationsDone++
|
|
||||||
return NopStatement.insteadOf(assignment)
|
|
||||||
}
|
|
||||||
if ((targetDt == DataType.UWORD && cv > 15.0) || (targetDt == DataType.UBYTE && cv > 7.0)) {
|
|
||||||
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
|
|
||||||
assignment.value.linkParents(assignment)
|
|
||||||
optimizationsDone++
|
|
||||||
} else {
|
|
||||||
// replace by in-place lsr(...) call
|
|
||||||
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
|
||||||
var numshifts = cv.toInt()
|
|
||||||
while (numshifts > 0) {
|
|
||||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
|
|
||||||
mutableListOf(bexpr.left), true, assignment.position))
|
|
||||||
numshifts--
|
|
||||||
}
|
|
||||||
optimizationsDone++
|
|
||||||
return scope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return super.visit(assignment)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(scope: AnonymousScope): Statement {
|
|
||||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
|
||||||
if(linesToRemove.isNotEmpty()) {
|
|
||||||
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
|
|
||||||
}
|
|
||||||
return super.visit(scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(label: Label): Statement {
|
|
||||||
// remove duplicate labels
|
|
||||||
val stmts = label.definingScope().statements
|
|
||||||
val startIdx = stmts.indexOf(label)
|
|
||||||
if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label)
|
|
||||||
return NopStatement.insteadOf(label)
|
|
||||||
|
|
||||||
return super.visit(label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal class FlattenAnonymousScopesAndRemoveNops: IAstVisitor {
|
|
||||||
private var scopesToFlatten = mutableListOf<INameScope>()
|
|
||||||
private val nopStatements = mutableListOf<NopStatement>()
|
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
|
||||||
super.visit(program)
|
|
||||||
for(scope in scopesToFlatten.reversed()) {
|
|
||||||
val namescope = scope.parent as INameScope
|
|
||||||
val idx = namescope.statements.indexOf(scope as Statement)
|
|
||||||
if(idx>=0) {
|
|
||||||
val nop = NopStatement.insteadOf(namescope.statements[idx])
|
|
||||||
nop.parent = namescope as Node
|
|
||||||
namescope.statements[idx] = nop
|
|
||||||
namescope.statements.addAll(idx, scope.statements)
|
|
||||||
scope.statements.forEach { it.parent = namescope }
|
|
||||||
visit(nop)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.nopStatements.forEach {
|
|
||||||
it.definingScope().remove(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(scope: AnonymousScope) {
|
|
||||||
if(scope.parent is INameScope) {
|
|
||||||
scopesToFlatten.add(scope) // get rid of the anonymous scope
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.visit(scope)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(nopStatement: NopStatement) {
|
|
||||||
nopStatements.add(nopStatement)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
38
compiler/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
38
compiler/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package prog8.optimizer
|
||||||
|
|
||||||
|
import prog8.ast.Node
|
||||||
|
import prog8.ast.Program
|
||||||
|
import prog8.ast.processing.AstWalker
|
||||||
|
import prog8.ast.processing.IAstModification
|
||||||
|
import prog8.ast.statements.Block
|
||||||
|
|
||||||
|
|
||||||
|
internal class UnusedCodeRemover: AstWalker() {
|
||||||
|
|
||||||
|
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
|
||||||
|
val callgraph = CallGraph(program)
|
||||||
|
val removals = mutableListOf<IAstModification>()
|
||||||
|
|
||||||
|
// 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.keepAlways && (callgraph.calledBy[sub].isNullOrEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
||||||
|
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||||
|
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||||
|
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||||
|
program.modules.forEach {
|
||||||
|
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||||
|
removals.add(IAstModification.Remove(it, it.parent))
|
||||||
|
}
|
||||||
|
|
||||||
|
return removals
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import org.antlr.v4.runtime.*
|
|||||||
import prog8.ast.Module
|
import prog8.ast.Module
|
||||||
import prog8.ast.Program
|
import prog8.ast.Program
|
||||||
import prog8.ast.antlr.toAst
|
import prog8.ast.antlr.toAst
|
||||||
|
import prog8.ast.base.ErrorReporter
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.base.SyntaxError
|
import prog8.ast.base.SyntaxError
|
||||||
import prog8.ast.base.checkImportedValid
|
import prog8.ast.base.checkImportedValid
|
||||||
@ -33,7 +34,9 @@ internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexe
|
|||||||
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
|
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
|
||||||
|
|
||||||
|
|
||||||
internal fun importModule(program: Program, filePath: Path): Module {
|
internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||||
|
|
||||||
|
internal fun importModule(program: Program, filePath: Path): Module {
|
||||||
print("importing '${moduleName(filePath.fileName)}'")
|
print("importing '${moduleName(filePath.fileName)}'")
|
||||||
if(filePath.parent!=null) {
|
if(filePath.parent!=null) {
|
||||||
var importloc = filePath.toString()
|
var importloc = filePath.toString()
|
||||||
@ -49,16 +52,16 @@ internal fun importModule(program: Program, filePath: Path): Module {
|
|||||||
|
|
||||||
val input = CharStreams.fromPath(filePath)
|
val input = CharStreams.fromPath(filePath)
|
||||||
return importModule(program, input, filePath, false)
|
return importModule(program, input, filePath, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun importLibraryModule(program: Program, name: String): Module? {
|
internal fun importLibraryModule(program: Program, name: String): Module? {
|
||||||
val import = Directive("%import", listOf(
|
val import = Directive("%import", listOf(
|
||||||
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
||||||
), Position("<<<implicit-import>>>", 0, 0, 0))
|
), Position("<<<implicit-import>>>", 0, 0, 0))
|
||||||
return executeImportDirective(program, import, Paths.get(""))
|
return executeImportDirective(program, import, Paths.get(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
|
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
|
||||||
val moduleName = moduleName(modulePath.fileName)
|
val moduleName = moduleName(modulePath.fileName)
|
||||||
val lexer = CustomLexer(modulePath, stream)
|
val lexer = CustomLexer(modulePath, stream)
|
||||||
val lexerErrors = LexerErrorListener()
|
val lexerErrors = LexerErrorListener()
|
||||||
@ -88,9 +91,9 @@ internal fun importModule(program: Program, stream: CharStream, modulePath: Path
|
|||||||
|
|
||||||
moduleAst.statements = lines
|
moduleAst.statements = lines
|
||||||
return moduleAst
|
return moduleAst
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
||||||
val fileName = "$name.p8"
|
val fileName = "$name.p8"
|
||||||
val locations = mutableListOf(source.parent)
|
val locations = mutableListOf(source.parent)
|
||||||
|
|
||||||
@ -108,9 +111,9 @@ private fun discoverImportedModuleFile(name: String, source: Path, position: Pos
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
|
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
|
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
|
||||||
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
|
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
|
||||||
throw SyntaxError("invalid import directive", import.position)
|
throw SyntaxError("invalid import directive", import.position)
|
||||||
val moduleName = import.args[0].name!!
|
val moduleName = import.args[0].name!!
|
||||||
@ -139,8 +142,9 @@ private fun executeImportDirective(program: Program, import: Directive, source:
|
|||||||
|
|
||||||
importedModule.checkImportedValid()
|
importedModule.checkImportedValid()
|
||||||
return importedModule
|
return importedModule
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
private fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
22
compiler/src/prog8/server/dbus/IrmenDbusTest.kt
Normal file
22
compiler/src/prog8/server/dbus/IrmenDbusTest.kt
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package prog8.server.dbus
|
||||||
|
|
||||||
|
//import org.freedesktop.dbus.interfaces.DBusInterface
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//interface IrmenDbusTest: DBusInterface
|
||||||
|
//{
|
||||||
|
// fun Status(address: String): Map<Int, String>
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//internal class TestService: IrmenDbusTest {
|
||||||
|
// override fun Status(address: String): Map<Int, String> {
|
||||||
|
// return mapOf(
|
||||||
|
// 5 to "hello",
|
||||||
|
// 42 to address
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun isRemote() = true
|
||||||
|
// override fun getObjectPath() = "/razorvine/TestService"
|
||||||
|
//}
|
17
compiler/src/prog8/server/dbus/clientmain.kt
Normal file
17
compiler/src/prog8/server/dbus/clientmain.kt
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package prog8.server.dbus
|
||||||
|
|
||||||
|
|
||||||
|
//import org.freedesktop.dbus.connections.impl.DBusConnection
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//fun main() {
|
||||||
|
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
|
||||||
|
// println(it.names.toList())
|
||||||
|
// println(it.uniqueName)
|
||||||
|
// println(it.address)
|
||||||
|
// println(it.machineId)
|
||||||
|
// val obj = it.getRemoteObject("local.net.razorvine.dbus.test", "/razorvine/TestService", IrmenDbusTest::class.java)
|
||||||
|
// println(obj.Status("irmen"))
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//
|
18
compiler/src/prog8/server/dbus/testdbus.kt
Normal file
18
compiler/src/prog8/server/dbus/testdbus.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package prog8.server.dbus
|
||||||
|
|
||||||
|
//import org.freedesktop.dbus.connections.impl.DBusConnection
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//fun main() {
|
||||||
|
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
|
||||||
|
// it.requestBusName("local.net.razorvine.dbus.test")
|
||||||
|
// println(it.names.toList())
|
||||||
|
// println(it.uniqueName)
|
||||||
|
// println(it.address)
|
||||||
|
// println(it.machineId)
|
||||||
|
// val service = TestService()
|
||||||
|
// it.exportObject(service.objectPath, service)
|
||||||
|
//
|
||||||
|
// Thread.sleep(100000)
|
||||||
|
// }
|
||||||
|
//}
|
@ -1,658 +0,0 @@
|
|||||||
package prog8.vm
|
|
||||||
|
|
||||||
import prog8.ast.base.ByteDatatypes
|
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.ast.base.WordDatatypes
|
|
||||||
import prog8.ast.expressions.ArrayLiteralValue
|
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
|
||||||
import prog8.vm.astvm.VmExecutionException
|
|
||||||
import java.util.Objects
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.pow
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rather than a literal value (NumericLiteralValue) that occurs in the parsed source code,
|
|
||||||
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form)
|
|
||||||
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
|
|
||||||
*/
|
|
||||||
|
|
||||||
abstract class RuntimeValueBase(val type: DataType) {
|
|
||||||
abstract fun numericValue(): Number
|
|
||||||
abstract fun integerValue(): Int
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class RuntimeValueNumeric(type: DataType, num: Number): RuntimeValueBase(type) {
|
|
||||||
|
|
||||||
val byteval: Short?
|
|
||||||
val wordval: Int?
|
|
||||||
val floatval: Double?
|
|
||||||
val asBoolean: Boolean
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromLv(literalValue: NumericLiteralValue): RuntimeValueNumeric {
|
|
||||||
return RuntimeValueNumeric(literalValue.type, num = literalValue.number)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
when (type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
val inum = num.toInt()
|
|
||||||
require(inum in 0..255) { "invalid value for ubyte: $inum" }
|
|
||||||
byteval = inum.toShort()
|
|
||||||
wordval = null
|
|
||||||
floatval = null
|
|
||||||
asBoolean = byteval != 0.toShort()
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val inum = num.toInt()
|
|
||||||
require(inum in -128..127) { "invalid value for byte: $inum" }
|
|
||||||
byteval = inum.toShort()
|
|
||||||
wordval = null
|
|
||||||
floatval = null
|
|
||||||
asBoolean = byteval != 0.toShort()
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
val inum = num.toInt()
|
|
||||||
require(inum in 0..65535) { "invalid value for uword: $inum" }
|
|
||||||
wordval = inum
|
|
||||||
byteval = null
|
|
||||||
floatval = null
|
|
||||||
asBoolean = wordval != 0
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
val inum = num.toInt()
|
|
||||||
require(inum in -32768..32767) { "invalid value for word: $inum" }
|
|
||||||
wordval = inum
|
|
||||||
byteval = null
|
|
||||||
floatval = null
|
|
||||||
asBoolean = wordval != 0
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
floatval = num.toDouble()
|
|
||||||
byteval = null
|
|
||||||
wordval = null
|
|
||||||
asBoolean = floatval != 0.0
|
|
||||||
}
|
|
||||||
else -> throw VmExecutionException("not a numeric value")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE -> "ub:%02x".format(byteval)
|
|
||||||
DataType.BYTE -> {
|
|
||||||
if (byteval!! < 0)
|
|
||||||
"b:-%02x".format(abs(byteval.toInt()))
|
|
||||||
else
|
|
||||||
"b:%02x".format(byteval)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> "uw:%04x".format(wordval)
|
|
||||||
DataType.WORD -> {
|
|
||||||
if (wordval!! < 0)
|
|
||||||
"w:-%04x".format(abs(wordval))
|
|
||||||
else
|
|
||||||
"w:%04x".format(wordval)
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> "f:$floatval"
|
|
||||||
else -> "???"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun numericValue(): Number {
|
|
||||||
return when (type) {
|
|
||||||
in ByteDatatypes -> byteval!!
|
|
||||||
in WordDatatypes -> wordval!!
|
|
||||||
DataType.FLOAT -> floatval!!
|
|
||||||
else -> throw ArithmeticException("invalid datatype for numeric value: $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun integerValue(): Int {
|
|
||||||
return when (type) {
|
|
||||||
in ByteDatatypes -> byteval!!.toInt()
|
|
||||||
in WordDatatypes -> wordval!!
|
|
||||||
DataType.FLOAT -> throw ArithmeticException("float to integer loss of precision")
|
|
||||||
else -> throw ArithmeticException("invalid datatype for integer value: $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = Objects.hash(byteval, wordval, floatval, type)
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (other == null || other !is RuntimeValueNumeric)
|
|
||||||
return false
|
|
||||||
return compareTo(other) == 0 // note: datatype doesn't matter
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun compareTo(other: RuntimeValueNumeric): Int = numericValue().toDouble().compareTo(other.numericValue().toDouble())
|
|
||||||
|
|
||||||
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): RuntimeValueNumeric {
|
|
||||||
if (leftDt != rightDt)
|
|
||||||
throw ArithmeticException("left and right datatypes are not the same")
|
|
||||||
if (result.toDouble() < 0) {
|
|
||||||
return when (leftDt) {
|
|
||||||
DataType.UBYTE, DataType.UWORD -> {
|
|
||||||
// storing a negative number in an unsigned one is done by storing the 2's complement instead
|
|
||||||
val number = abs(result.toDouble().toInt())
|
|
||||||
if (leftDt == DataType.UBYTE)
|
|
||||||
RuntimeValueNumeric(DataType.UBYTE, (number xor 255) + 1)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(DataType.UWORD, (number xor 65535) + 1)
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val v = result.toInt() and 255
|
|
||||||
if (v < 128)
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, v)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, v - 256)
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
val v = result.toInt() and 65535
|
|
||||||
if (v < 32768)
|
|
||||||
RuntimeValueNumeric(DataType.WORD, v)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(DataType.WORD, v - 65536)
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
|
|
||||||
else -> throw ArithmeticException("$op on non-numeric type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return when (leftDt) {
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result.toInt() and 255)
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val v = result.toInt() and 255
|
|
||||||
if (v < 128)
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, v)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, v - 256)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, result.toInt() and 65535)
|
|
||||||
DataType.WORD -> {
|
|
||||||
val v = result.toInt() and 65535
|
|
||||||
if (v < 32768)
|
|
||||||
RuntimeValueNumeric(DataType.WORD, v)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(DataType.WORD, v - 65536)
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
|
|
||||||
else -> throw ArithmeticException("$op on non-numeric type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun add(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
|
||||||
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
|
|
||||||
throw ArithmeticException("floating point loss of precision on type $type")
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
val result = v1.toDouble() + v2.toDouble()
|
|
||||||
return arithResult(type, result, other.type, "add")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sub(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
|
||||||
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
|
|
||||||
throw ArithmeticException("floating point loss of precision on type $type")
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
val result = v1.toDouble() - v2.toDouble()
|
|
||||||
return arithResult(type, result, other.type, "sub")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mul(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
|
||||||
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
|
|
||||||
throw ArithmeticException("floating point loss of precision on type $type")
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
val result = v1.toDouble() * v2.toDouble()
|
|
||||||
return arithResult(type, result, other.type, "mul")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun div(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
|
||||||
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
|
|
||||||
throw ArithmeticException("floating point loss of precision on type $type")
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
if (v2.toDouble() == 0.0) {
|
|
||||||
when (type) {
|
|
||||||
DataType.UBYTE -> return RuntimeValueNumeric(DataType.UBYTE, 255)
|
|
||||||
DataType.BYTE -> return RuntimeValueNumeric(DataType.BYTE, 127)
|
|
||||||
DataType.UWORD -> return RuntimeValueNumeric(DataType.UWORD, 65535)
|
|
||||||
DataType.WORD -> return RuntimeValueNumeric(DataType.WORD, 32767)
|
|
||||||
else -> {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val result = v1.toDouble() / v2.toDouble()
|
|
||||||
// NOTE: integer division returns integer result!
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result)
|
|
||||||
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, result)
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, result)
|
|
||||||
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, result)
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
|
|
||||||
else -> throw ArithmeticException("div on non-numeric type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remainder(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
val result = v1.toDouble() % v2.toDouble()
|
|
||||||
return arithResult(type, result, other.type, "remainder")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pow(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
|
||||||
val v1 = numericValue()
|
|
||||||
val v2 = other.numericValue()
|
|
||||||
val result = v1.toDouble().pow(v2.toDouble())
|
|
||||||
return arithResult(type, result, other.type, "pow")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shl(): RuntimeValueNumeric {
|
|
||||||
val v = integerValue()
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(type, (v shl 1) and 255)
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(type, (v shl 1) and 65535)
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val value = v shl 1
|
|
||||||
if (value < 128)
|
|
||||||
RuntimeValueNumeric(type, value)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(type, value - 256)
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
val value = v shl 1
|
|
||||||
if (value < 32768)
|
|
||||||
RuntimeValueNumeric(type, value)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(type, value - 65536)
|
|
||||||
}
|
|
||||||
else -> throw ArithmeticException("invalid type for shl: $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun shr(): RuntimeValueNumeric {
|
|
||||||
val v = integerValue()
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(type, v ushr 1)
|
|
||||||
DataType.BYTE -> RuntimeValueNumeric(type, v shr 1)
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(type, v ushr 1)
|
|
||||||
DataType.WORD -> RuntimeValueNumeric(type, v shr 1)
|
|
||||||
else -> throw ArithmeticException("invalid type for shr: $type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rol(carry: Boolean): Pair<RuntimeValueNumeric, Boolean> {
|
|
||||||
// 9 or 17 bit rotate left (with carry))
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
|
||||||
val v = byteval!!.toInt()
|
|
||||||
val newCarry = (v and 0x80) != 0
|
|
||||||
val newval = (v and 0x7f shl 1) or (if (carry) 1 else 0)
|
|
||||||
Pair(RuntimeValueNumeric(DataType.UBYTE, newval), newCarry)
|
|
||||||
}
|
|
||||||
DataType.UWORD, DataType.WORD -> {
|
|
||||||
val v = wordval!!
|
|
||||||
val newCarry = (v and 0x8000) != 0
|
|
||||||
val newval = (v and 0x7fff shl 1) or (if (carry) 1 else 0)
|
|
||||||
Pair(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
|
|
||||||
}
|
|
||||||
else -> throw ArithmeticException("rol can only work on byte/word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ror(carry: Boolean): Pair<RuntimeValueNumeric, Boolean> {
|
|
||||||
// 9 or 17 bit rotate right (with carry)
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
|
||||||
val v = byteval!!.toInt()
|
|
||||||
val newCarry = v and 1 != 0
|
|
||||||
val newval = (v ushr 1) or (if (carry) 0x80 else 0)
|
|
||||||
Pair(RuntimeValueNumeric(DataType.UBYTE, newval), newCarry)
|
|
||||||
}
|
|
||||||
DataType.UWORD, DataType.WORD -> {
|
|
||||||
val v = wordval!!
|
|
||||||
val newCarry = v and 1 != 0
|
|
||||||
val newval = (v ushr 1) or (if (carry) 0x8000 else 0)
|
|
||||||
Pair(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
|
|
||||||
}
|
|
||||||
else -> throw ArithmeticException("ror2 can only work on byte/word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rol2(): RuntimeValueNumeric {
|
|
||||||
// 8 or 16 bit rotate left
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
|
||||||
val v = byteval!!.toInt()
|
|
||||||
val carry = (v and 0x80) ushr 7
|
|
||||||
val newval = (v and 0x7f shl 1) or carry
|
|
||||||
RuntimeValueNumeric(DataType.UBYTE, newval)
|
|
||||||
}
|
|
||||||
DataType.UWORD, DataType.WORD -> {
|
|
||||||
val v = wordval!!
|
|
||||||
val carry = (v and 0x8000) ushr 15
|
|
||||||
val newval = (v and 0x7fff shl 1) or carry
|
|
||||||
RuntimeValueNumeric(DataType.UWORD, newval)
|
|
||||||
}
|
|
||||||
else -> throw ArithmeticException("rol2 can only work on byte/word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ror2(): RuntimeValueNumeric {
|
|
||||||
// 8 or 16 bit rotate right
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE, DataType.BYTE -> {
|
|
||||||
val v = byteval!!.toInt()
|
|
||||||
val carry = v and 1 shl 7
|
|
||||||
val newval = (v ushr 1) or carry
|
|
||||||
RuntimeValueNumeric(DataType.UBYTE, newval)
|
|
||||||
}
|
|
||||||
DataType.UWORD, DataType.WORD -> {
|
|
||||||
val v = wordval!!
|
|
||||||
val carry = v and 1 shl 15
|
|
||||||
val newval = (v ushr 1) or carry
|
|
||||||
RuntimeValueNumeric(DataType.UWORD, newval)
|
|
||||||
}
|
|
||||||
else -> throw ArithmeticException("ror2 can only work on byte/word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun neg(): RuntimeValueNumeric {
|
|
||||||
return when (type) {
|
|
||||||
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, -(byteval!!))
|
|
||||||
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, -(wordval!!))
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, -(floatval)!!)
|
|
||||||
else -> throw ArithmeticException("neg can only work on byte/word/float")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun abs(): RuntimeValueNumeric {
|
|
||||||
return when (type) {
|
|
||||||
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, abs(byteval!!.toInt()))
|
|
||||||
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, abs(wordval!!))
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, abs(floatval!!))
|
|
||||||
else -> throw ArithmeticException("abs can only work on byte/word/float")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bitand(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
|
||||||
val v1 = integerValue()
|
|
||||||
val v2 = other.integerValue()
|
|
||||||
val result = v1 and v2
|
|
||||||
return RuntimeValueNumeric(type, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bitor(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
|
||||||
val v1 = integerValue()
|
|
||||||
val v2 = other.integerValue()
|
|
||||||
val result = v1 or v2
|
|
||||||
return RuntimeValueNumeric(type, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bitxor(other: RuntimeValueNumeric): RuntimeValueNumeric {
|
|
||||||
val v1 = integerValue()
|
|
||||||
val v2 = other.integerValue()
|
|
||||||
val result = v1 xor v2
|
|
||||||
return RuntimeValueNumeric(type, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun and(other: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean && other.asBoolean) 1 else 0)
|
|
||||||
fun or(other: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean || other.asBoolean) 1 else 0)
|
|
||||||
fun xor(other: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean xor other.asBoolean) 1 else 0)
|
|
||||||
fun not() = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean) 0 else 1)
|
|
||||||
|
|
||||||
fun inv(): RuntimeValueNumeric {
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(type, byteval!!.toInt().inv() and 255)
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(type, wordval!!.inv() and 65535)
|
|
||||||
DataType.BYTE -> RuntimeValueNumeric(type, byteval!!.toInt().inv())
|
|
||||||
DataType.WORD -> RuntimeValueNumeric(type, wordval!!.inv())
|
|
||||||
else -> throw ArithmeticException("inv can only work on byte/word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun inc(): RuntimeValueNumeric {
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(type, (byteval!! + 1) and 255)
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(type, (wordval!! + 1) and 65535)
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val newval = byteval!! + 1
|
|
||||||
if (newval == 128)
|
|
||||||
RuntimeValueNumeric(type, -128)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(type, newval)
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
val newval = wordval!! + 1
|
|
||||||
if (newval == 32768)
|
|
||||||
RuntimeValueNumeric(type, -32768)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(type, newval)
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, floatval!! + 1)
|
|
||||||
else -> throw ArithmeticException("inc can only work on numeric types")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun dec(): RuntimeValueNumeric {
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(type, (byteval!! - 1) and 255)
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(type, (wordval!! - 1) and 65535)
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val newval = byteval!! - 1
|
|
||||||
if (newval == -129)
|
|
||||||
RuntimeValueNumeric(type, 127)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(type, newval)
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
val newval = wordval!! - 1
|
|
||||||
if (newval == -32769)
|
|
||||||
RuntimeValueNumeric(type, 32767)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(type, newval)
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, floatval!! - 1)
|
|
||||||
else -> throw ArithmeticException("dec can only work on numeric types")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun msb(): RuntimeValueNumeric {
|
|
||||||
return when (type) {
|
|
||||||
in ByteDatatypes -> RuntimeValueNumeric(DataType.UBYTE, 0)
|
|
||||||
in WordDatatypes -> RuntimeValueNumeric(DataType.UBYTE, wordval!! ushr 8 and 255)
|
|
||||||
else -> throw ArithmeticException("msb can only work on (u)byte/(u)word")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cast(targetType: DataType): RuntimeValueNumeric {
|
|
||||||
return when (type) {
|
|
||||||
DataType.UBYTE -> {
|
|
||||||
when (targetType) {
|
|
||||||
DataType.UBYTE -> this
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val nval = byteval!!.toInt()
|
|
||||||
if (nval < 128)
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, nval)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, nval - 256)
|
|
||||||
}
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, numericValue())
|
|
||||||
DataType.WORD -> {
|
|
||||||
val nval = numericValue().toInt()
|
|
||||||
if (nval < 32768)
|
|
||||||
RuntimeValueNumeric(DataType.WORD, nval)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(DataType.WORD, nval - 65536)
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
|
|
||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.BYTE -> {
|
|
||||||
when (targetType) {
|
|
||||||
DataType.BYTE -> this
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 255)
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue() and 65535)
|
|
||||||
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, integerValue())
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
|
|
||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.UWORD -> {
|
|
||||||
when (targetType) {
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val v = integerValue()
|
|
||||||
if (v < 128)
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, v)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, v - 256)
|
|
||||||
}
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 255)
|
|
||||||
DataType.UWORD -> this
|
|
||||||
DataType.WORD -> {
|
|
||||||
val v = integerValue()
|
|
||||||
if (v < 32768)
|
|
||||||
RuntimeValueNumeric(DataType.WORD, v)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(DataType.WORD, v - 65536)
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
|
|
||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.WORD -> {
|
|
||||||
when (targetType) {
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val v = integerValue() and 255
|
|
||||||
if (v < 128)
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, v)
|
|
||||||
else
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, v - 256)
|
|
||||||
}
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 65535)
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue())
|
|
||||||
DataType.WORD -> this
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
|
|
||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> {
|
|
||||||
when (targetType) {
|
|
||||||
DataType.BYTE -> {
|
|
||||||
val integer = numericValue().toInt()
|
|
||||||
if (integer in -128..127)
|
|
||||||
RuntimeValueNumeric(DataType.BYTE, integer)
|
|
||||||
else
|
|
||||||
throw ArithmeticException("overflow when casting float to byte: $this")
|
|
||||||
}
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, numericValue().toInt())
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, numericValue().toInt())
|
|
||||||
DataType.WORD -> {
|
|
||||||
val integer = numericValue().toInt()
|
|
||||||
if (integer in -32768..32767)
|
|
||||||
RuntimeValueNumeric(DataType.WORD, integer)
|
|
||||||
else
|
|
||||||
throw ArithmeticException("overflow when casting float to word: $this")
|
|
||||||
}
|
|
||||||
DataType.FLOAT -> this
|
|
||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class RuntimeValueString(val str: String, val heapId: Int?): RuntimeValueBase(DataType.STR) {
|
|
||||||
companion object {
|
|
||||||
fun fromLv(string: StringLiteralValue): RuntimeValueString {
|
|
||||||
return RuntimeValueString(string.value, string.heapId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = if(type==DataType.STR) "str:$str" else "???"
|
|
||||||
|
|
||||||
override fun hashCode(): Int = Objects.hash(type, str)
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (other == null || other !is RuntimeValueString)
|
|
||||||
return false
|
|
||||||
return type == other.type && str == other.str
|
|
||||||
}
|
|
||||||
|
|
||||||
fun iterator(): Iterator<Number> = str.map { it.toShort() }.iterator()
|
|
||||||
|
|
||||||
override fun numericValue(): Number {
|
|
||||||
throw VmExecutionException("string is not a number")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun integerValue(): Int {
|
|
||||||
throw VmExecutionException("string is not a number")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
open class RuntimeValueArray(type: DataType, val array: Array<Number>, val heapId: Int?): RuntimeValueBase(type) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun fromLv(array: ArrayLiteralValue): RuntimeValueArray {
|
|
||||||
return if (array.type == DataType.ARRAY_F) {
|
|
||||||
val doubleArray = array.value.map { (it as NumericLiteralValue).number }.toTypedArray()
|
|
||||||
RuntimeValueArray(array.type, doubleArray, array.heapId)
|
|
||||||
} else {
|
|
||||||
val resultArray = mutableListOf<Number>()
|
|
||||||
for (elt in array.value.withIndex()) {
|
|
||||||
if (elt.value is NumericLiteralValue)
|
|
||||||
resultArray.add((elt.value as NumericLiteralValue).number.toInt())
|
|
||||||
else {
|
|
||||||
resultArray.add((elt.hashCode())) // ...poor man's implementation of ADDRESSOF(array), it probably won't work very well
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RuntimeValueArray(array.type, resultArray.toTypedArray(), array.heapId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String {
|
|
||||||
return when (type) {
|
|
||||||
DataType.ARRAY_UB -> "array_ub:..."
|
|
||||||
DataType.ARRAY_B -> "array_b:..."
|
|
||||||
DataType.ARRAY_UW -> "array_uw:..."
|
|
||||||
DataType.ARRAY_W -> "array_w:..."
|
|
||||||
DataType.ARRAY_F -> "array_f:..."
|
|
||||||
else -> "???"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int = Objects.hash(type, array)
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
if (other == null || other !is RuntimeValueArray)
|
|
||||||
return false
|
|
||||||
return type == other.type && array.contentEquals(other.array)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun iterator(): Iterator<Number> = array.iterator()
|
|
||||||
|
|
||||||
override fun numericValue(): Number {
|
|
||||||
throw VmExecutionException("array is not a number")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun integerValue(): Int {
|
|
||||||
throw VmExecutionException("array is not a number")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValueArray(type, range.toList().toTypedArray(), null) {
|
|
||||||
override fun iterator(): Iterator<Number> {
|
|
||||||
return range.iterator()
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,177 +0,0 @@
|
|||||||
package prog8.vm.astvm
|
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.ArrayElementTypes
|
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.ast.base.FatalAstException
|
|
||||||
import prog8.ast.base.VarDeclType
|
|
||||||
import prog8.ast.expressions.*
|
|
||||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
|
||||||
import prog8.ast.statements.Label
|
|
||||||
import prog8.ast.statements.Subroutine
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.vm.*
|
|
||||||
|
|
||||||
|
|
||||||
typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValueNumeric>, flags: StatusFlags) -> RuntimeValueNumeric?
|
|
||||||
typealias SubroutineCaller = (sub: Subroutine, args: List<RuntimeValueNumeric>, startAtLabel: Label?) -> RuntimeValueNumeric?
|
|
||||||
|
|
||||||
|
|
||||||
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
|
|
||||||
val runtimeVars: RuntimeVariables,
|
|
||||||
val performBuiltinFunction: BuiltinfunctionCaller,
|
|
||||||
val executeSubroutine: SubroutineCaller)
|
|
||||||
|
|
||||||
fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValueBase {
|
|
||||||
val constval = expr.constValue(ctx.program)
|
|
||||||
if(constval!=null)
|
|
||||||
return RuntimeValueNumeric.fromLv(constval)
|
|
||||||
|
|
||||||
when(expr) {
|
|
||||||
is NumericLiteralValue -> return RuntimeValueNumeric.fromLv(expr)
|
|
||||||
is StringLiteralValue -> return RuntimeValueString.fromLv(expr)
|
|
||||||
is ArrayLiteralValue -> return RuntimeValueArray.fromLv(expr)
|
|
||||||
is PrefixExpression -> {
|
|
||||||
return when(expr.operator) {
|
|
||||||
"-" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).neg()
|
|
||||||
"~" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).inv()
|
|
||||||
"not" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).not()
|
|
||||||
// unary '+' should have been optimized away
|
|
||||||
else -> throw VmExecutionException("unsupported prefix operator "+expr.operator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is BinaryExpression -> {
|
|
||||||
val left = evaluate(expr.left, ctx) as RuntimeValueNumeric
|
|
||||||
val right = evaluate(expr.right, ctx) as RuntimeValueNumeric
|
|
||||||
return when(expr.operator) {
|
|
||||||
"<" -> RuntimeValueNumeric(DataType.UBYTE, if (left < right) 1 else 0)
|
|
||||||
"<=" -> RuntimeValueNumeric(DataType.UBYTE, if (left <= right) 1 else 0)
|
|
||||||
">" -> RuntimeValueNumeric(DataType.UBYTE, if (left > right) 1 else 0)
|
|
||||||
">=" -> RuntimeValueNumeric(DataType.UBYTE, if (left >= right) 1 else 0)
|
|
||||||
"==" -> RuntimeValueNumeric(DataType.UBYTE, if (left == right) 1 else 0)
|
|
||||||
"!=" -> RuntimeValueNumeric(DataType.UBYTE, if (left != right) 1 else 0)
|
|
||||||
"+" -> left.add(right)
|
|
||||||
"-" -> left.sub(right)
|
|
||||||
"*" -> left.mul(right)
|
|
||||||
"/" -> left.div(right)
|
|
||||||
"**" -> left.pow(right)
|
|
||||||
"<<" -> {
|
|
||||||
var result = left
|
|
||||||
repeat(right.integerValue()) {result = result.shl()}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
">>" -> {
|
|
||||||
var result = left
|
|
||||||
repeat(right.integerValue()) {result = result.shr()}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
"%" -> left.remainder(right)
|
|
||||||
"|" -> left.bitor(right)
|
|
||||||
"&" -> left.bitand(right)
|
|
||||||
"^" -> left.bitxor(right)
|
|
||||||
"and" -> left.and(right)
|
|
||||||
"or" -> left.or(right)
|
|
||||||
"xor" -> left.xor(right)
|
|
||||||
else -> throw VmExecutionException("unsupported operator "+expr.operator)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is ArrayIndexedExpression -> {
|
|
||||||
val array = evaluate(expr.identifier, ctx)
|
|
||||||
val index = evaluate(expr.arrayspec.index, ctx) as RuntimeValueNumeric
|
|
||||||
return when (array) {
|
|
||||||
is RuntimeValueString -> {
|
|
||||||
val value = array.str[index.integerValue()]
|
|
||||||
RuntimeValueNumeric(ArrayElementTypes.getValue(array.type), value.toShort())
|
|
||||||
}
|
|
||||||
is RuntimeValueArray -> {
|
|
||||||
val value = array.array[index.integerValue()]
|
|
||||||
RuntimeValueNumeric(ArrayElementTypes.getValue(array.type), value)
|
|
||||||
}
|
|
||||||
else -> throw VmExecutionException("weird type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TypecastExpression -> {
|
|
||||||
return (evaluate(expr.expression, ctx) as RuntimeValueNumeric).cast(expr.type)
|
|
||||||
}
|
|
||||||
is AddressOf -> {
|
|
||||||
// we support: address of heap var -> the heap id
|
|
||||||
return try {
|
|
||||||
val heapId = expr.identifier.heapId(ctx.program.namespace)
|
|
||||||
RuntimeValueNumeric(DataType.UWORD, heapId)
|
|
||||||
} catch( f: FatalAstException) {
|
|
||||||
// fallback: use the hash of the name, so we have at least *a* value...
|
|
||||||
val address = expr.identifier.hashCode() and 65535
|
|
||||||
RuntimeValueNumeric(DataType.UWORD, address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is DirectMemoryRead -> {
|
|
||||||
val address = (evaluate(expr.addressExpression, ctx) as RuntimeValueNumeric).wordval!!
|
|
||||||
return RuntimeValueNumeric(DataType.UBYTE, ctx.mem.getUByte(address))
|
|
||||||
}
|
|
||||||
is RegisterExpr -> return ctx.runtimeVars.get(ctx.program.namespace, expr.register.name)
|
|
||||||
is IdentifierReference -> {
|
|
||||||
val scope = expr.definingScope()
|
|
||||||
val variable = scope.lookup(expr.nameInSource, expr)
|
|
||||||
if(variable is VarDecl) {
|
|
||||||
when {
|
|
||||||
variable.type==VarDeclType.VAR -> return ctx.runtimeVars.get(variable.definingScope(), variable.name)
|
|
||||||
variable.datatype==DataType.STRUCT -> throw VmExecutionException("cannot process structs by-value. at ${expr.position}")
|
|
||||||
else -> {
|
|
||||||
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
|
|
||||||
return when(variable.datatype) {
|
|
||||||
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, ctx.mem.getUByte(address))
|
|
||||||
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, ctx.mem.getSByte(address))
|
|
||||||
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, ctx.mem.getUWord(address))
|
|
||||||
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, ctx.mem.getSWord(address))
|
|
||||||
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, ctx.mem.getFloat(address))
|
|
||||||
DataType.STR -> RuntimeValueString(ctx.mem.getString(address), null)
|
|
||||||
else -> throw VmExecutionException("unexpected datatype $variable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
throw VmExecutionException("weird identifier reference $variable")
|
|
||||||
}
|
|
||||||
is FunctionCall -> {
|
|
||||||
val sub = expr.target.targetStatement(ctx.program.namespace)
|
|
||||||
val args = expr.args.map { evaluate(it, ctx) as RuntimeValueNumeric }
|
|
||||||
return when(sub) {
|
|
||||||
is Subroutine -> {
|
|
||||||
val result = ctx.executeSubroutine(sub, args, null)
|
|
||||||
?: throw VmExecutionException("expected a result from functioncall $expr")
|
|
||||||
result
|
|
||||||
}
|
|
||||||
is BuiltinFunctionStatementPlaceholder -> {
|
|
||||||
val result = ctx.performBuiltinFunction(sub.name, args, ctx.statusflags)
|
|
||||||
?: throw VmExecutionException("expected 1 result from functioncall $expr")
|
|
||||||
result
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
throw VmExecutionException("unimplemented function call target $sub")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is RangeExpr -> {
|
|
||||||
val cRange = expr.toConstantIntegerRange()
|
|
||||||
if(cRange!=null) {
|
|
||||||
val dt = expr.inferType(ctx.program)
|
|
||||||
if(dt.isKnown)
|
|
||||||
return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), cRange)
|
|
||||||
else
|
|
||||||
throw VmExecutionException("couldn't determine datatype")
|
|
||||||
}
|
|
||||||
val fromVal = (evaluate(expr.from, ctx) as RuntimeValueNumeric).integerValue()
|
|
||||||
val toVal = (evaluate(expr.to, ctx) as RuntimeValueNumeric).integerValue()
|
|
||||||
val stepVal = (evaluate(expr.step, ctx) as RuntimeValueNumeric).integerValue()
|
|
||||||
val range = makeRange(fromVal, toVal, stepVal)
|
|
||||||
val dt = expr.inferType(ctx.program)
|
|
||||||
if(dt.isKnown)
|
|
||||||
return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), range)
|
|
||||||
else
|
|
||||||
throw VmExecutionException("couldn't determine datatype")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
throw VmExecutionException("unimplemented expression node $expr")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
package prog8.vm.astvm
|
|
||||||
|
|
||||||
import prog8.compiler.target.CompilationTarget
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
class Memory(private val readObserver: (address: Int, value: Short) -> Short,
|
|
||||||
private val writeObserver: (address: Int, value: Short) -> Short)
|
|
||||||
{
|
|
||||||
|
|
||||||
private val mem = ShortArray(65536) // shorts because byte is signed and we store values 0..255
|
|
||||||
private val observed = BooleanArray(65536) // what addresses are observed
|
|
||||||
|
|
||||||
|
|
||||||
fun observe(vararg address: Int) {
|
|
||||||
address.forEach { observed[it]=true }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUByte(address: Int): Short {
|
|
||||||
return if(observed[address]) readObserver(address, mem[address])
|
|
||||||
else mem[address]
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUByteDirectly(address: Int): Short {
|
|
||||||
return mem[address]
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSByte(address: Int): Short {
|
|
||||||
val ubyte = getUByte(address)
|
|
||||||
return if(ubyte <= 127) ubyte
|
|
||||||
else (-((ubyte.toInt() xor 255)+1)).toShort() // 2's complement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setUByte(address: Int, value: Short) {
|
|
||||||
if(value !in 0..255)
|
|
||||||
throw VmExecutionException("ubyte value out of range $value")
|
|
||||||
mem[address] =
|
|
||||||
if(observed[address]) writeObserver(address, value)
|
|
||||||
else value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setUByteDirectly(address: Int, value: Short) {
|
|
||||||
if(value !in 0..255)
|
|
||||||
throw VmExecutionException("ubyte value out of range $value")
|
|
||||||
mem[address] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSByte(address: Int, value: Short) {
|
|
||||||
if(value !in -128..127) throw VmExecutionException("byte value out of range $value")
|
|
||||||
val ubyte =
|
|
||||||
if(value>=0) value
|
|
||||||
else ((abs(value.toInt()) xor 255)+1).toShort() // 2's complement
|
|
||||||
setUByte(address, ubyte)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUWord(address: Int): Int {
|
|
||||||
return getUByte(address) + 256*getUByte(address+1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getSWord(address: Int): Int {
|
|
||||||
val uword = getUWord(address)
|
|
||||||
if(uword <= 32767)
|
|
||||||
return uword
|
|
||||||
return -((uword xor 65535)+1) // 2's complement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setUWord(address: Int, value: Int) {
|
|
||||||
if(value !in 0..65535)
|
|
||||||
throw VmExecutionException("uword value out of range $value")
|
|
||||||
setUByte(address, value.and(255).toShort())
|
|
||||||
setUByte(address+1, (value / 256).toShort())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSWord(address: Int, value: Int) {
|
|
||||||
if(value !in -32768..32767) throw VmExecutionException("word value out of range $value")
|
|
||||||
if(value>=0)
|
|
||||||
setUWord(address, value)
|
|
||||||
else
|
|
||||||
setUWord(address, (abs(value) xor 65535)+1) // 2's complement
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setFloat(address: Int, value: Double) {
|
|
||||||
val mflpt5 = C64MachineDefinition.Mflpt5.fromNumber(value)
|
|
||||||
setUByte(address, mflpt5.b0)
|
|
||||||
setUByte(address+1, mflpt5.b1)
|
|
||||||
setUByte(address+2, mflpt5.b2)
|
|
||||||
setUByte(address+3, mflpt5.b3)
|
|
||||||
setUByte(address+4, mflpt5.b4)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getFloat(address: Int): Double {
|
|
||||||
return C64MachineDefinition.Mflpt5(getUByte(address), getUByte(address + 1), getUByte(address + 2),
|
|
||||||
getUByte(address + 3), getUByte(address + 4)).toDouble()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setString(address: Int, str: String) {
|
|
||||||
val encoded = CompilationTarget.encodeString(str)
|
|
||||||
var addr = address
|
|
||||||
for (c in encoded) setUByte(addr++, c)
|
|
||||||
setUByte(addr, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getString(strAddress: Int): String {
|
|
||||||
val encoded = mutableListOf<Short>()
|
|
||||||
var addr = strAddress
|
|
||||||
while(true) {
|
|
||||||
val byte = getUByte(addr++)
|
|
||||||
if(byte==0.toShort()) break
|
|
||||||
encoded.add(byte)
|
|
||||||
}
|
|
||||||
return CompilationTarget.decodeString(encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clear() {
|
|
||||||
for(i in 0..65535) setUByte(i, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun copy(from: Int, to: Int, numbytes: Int) {
|
|
||||||
for(i in 0 until numbytes)
|
|
||||||
setUByte(to+i, getUByte(from+i))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,184 +0,0 @@
|
|||||||
package prog8.vm.astvm
|
|
||||||
|
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition
|
|
||||||
import prog8.compiler.target.c64.Petscii
|
|
||||||
import java.awt.*
|
|
||||||
import java.awt.event.KeyEvent
|
|
||||||
import java.awt.event.KeyListener
|
|
||||||
import java.awt.image.BufferedImage
|
|
||||||
import java.util.ArrayDeque
|
|
||||||
import javax.swing.JFrame
|
|
||||||
import javax.swing.JPanel
|
|
||||||
import javax.swing.Timer
|
|
||||||
|
|
||||||
|
|
||||||
class BitmapScreenPanel : KeyListener, JPanel() {
|
|
||||||
|
|
||||||
private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB)
|
|
||||||
private val g2d = image.graphics as Graphics2D
|
|
||||||
private var cursorX: Int=0
|
|
||||||
private var cursorY: Int=0
|
|
||||||
val keyboardBuffer = ArrayDeque<Char>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
val size = Dimension(image.width * SCALING, image.height * SCALING)
|
|
||||||
minimumSize = size
|
|
||||||
maximumSize = size
|
|
||||||
preferredSize = size
|
|
||||||
clearScreen(6)
|
|
||||||
isFocusable = true
|
|
||||||
requestFocusInWindow()
|
|
||||||
addKeyListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun keyTyped(p0: KeyEvent) {
|
|
||||||
keyboardBuffer.add(p0.keyChar)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun keyPressed(p0: KeyEvent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun keyReleased(p0: KeyEvent?) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun paint(graphics: Graphics?) {
|
|
||||||
val g2d = graphics as Graphics2D?
|
|
||||||
g2d!!.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF)
|
|
||||||
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE)
|
|
||||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
|
|
||||||
g2d.drawImage(image, 0, 0, image.width * 3, image.height * 3, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearScreen(color: Short) {
|
|
||||||
g2d.background = C64MachineDefinition.colorPalette[color % C64MachineDefinition.colorPalette.size]
|
|
||||||
g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT)
|
|
||||||
cursorX = 0
|
|
||||||
cursorY = 0
|
|
||||||
}
|
|
||||||
fun setPixel(x: Int, y: Int, color: Short) {
|
|
||||||
image.setRGB(x, y, C64MachineDefinition.colorPalette[color % C64MachineDefinition.colorPalette.size].rgb)
|
|
||||||
}
|
|
||||||
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) {
|
|
||||||
g2d.color = C64MachineDefinition.colorPalette[color % C64MachineDefinition.colorPalette.size]
|
|
||||||
g2d.drawLine(x1, y1, x2, y2)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun printAsciiText(text: String) {
|
|
||||||
val t2 = text.substringBefore(0.toChar())
|
|
||||||
val petscii = Petscii.encodePetscii(t2, true)
|
|
||||||
petscii.forEach { printPetsciiChar(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun printPetsciiChar(petscii: Short) {
|
|
||||||
if(petscii in listOf(0x0d.toShort(), 0x8d.toShort())) {
|
|
||||||
// Return and shift-Return
|
|
||||||
cursorX=0
|
|
||||||
cursorY++
|
|
||||||
} else {
|
|
||||||
val scr = Petscii.petscii2scr(petscii, false)
|
|
||||||
setScreenChar(cursorX, cursorY, scr, 1)
|
|
||||||
cursorX++
|
|
||||||
if (cursorX >= (SCREENWIDTH / 8)) {
|
|
||||||
cursorY++
|
|
||||||
cursorX = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while(cursorY>=(SCREENHEIGHT/8)) {
|
|
||||||
// scroll the screen up because the cursor went past the last line
|
|
||||||
Thread.sleep(10)
|
|
||||||
val screen = image.copy()
|
|
||||||
val graphics = image.graphics as Graphics2D
|
|
||||||
graphics.drawImage(screen, 0, -8, null)
|
|
||||||
val color = graphics.color
|
|
||||||
graphics.color = C64MachineDefinition.colorPalette[6]
|
|
||||||
graphics.fillRect(0, 24*8, SCREENWIDTH, 25*8)
|
|
||||||
graphics.color=color
|
|
||||||
cursorY--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setScreenChar(x: Int, y: Int, screencode: Short, color: Short) {
|
|
||||||
g2d.clearRect(8*x, 8*y, 8, 8)
|
|
||||||
val colorIdx = (color % C64MachineDefinition.colorPalette.size).toShort()
|
|
||||||
val coloredImage = C64MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
|
|
||||||
g2d.drawImage(coloredImage, 8*x, 8*y , null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setCursorPos(x: Int, y: Int) {
|
|
||||||
cursorX = x
|
|
||||||
cursorY = y
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCursorPos(): Pair<Int, Int> {
|
|
||||||
return Pair(cursorX, cursorY)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SCREENWIDTH = 320
|
|
||||||
const val SCREENHEIGHT = 200
|
|
||||||
const val SCALING = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ScreenDialog(title: String) : JFrame(title) {
|
|
||||||
val canvas = BitmapScreenPanel()
|
|
||||||
val keyboardBuffer = canvas.keyboardBuffer
|
|
||||||
|
|
||||||
init {
|
|
||||||
val borderWidth = 16
|
|
||||||
layout = GridBagLayout()
|
|
||||||
defaultCloseOperation = EXIT_ON_CLOSE
|
|
||||||
isResizable = false
|
|
||||||
|
|
||||||
// the borders (top, left, right, bottom)
|
|
||||||
val borderTop = JPanel().apply {
|
|
||||||
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
|
|
||||||
background = C64MachineDefinition.colorPalette[14]
|
|
||||||
}
|
|
||||||
val borderBottom = JPanel().apply {
|
|
||||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
|
|
||||||
background = C64MachineDefinition.colorPalette[14]
|
|
||||||
}
|
|
||||||
val borderLeft = JPanel().apply {
|
|
||||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
|
|
||||||
background = C64MachineDefinition.colorPalette[14]
|
|
||||||
}
|
|
||||||
val borderRight = JPanel().apply {
|
|
||||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
|
|
||||||
background = C64MachineDefinition.colorPalette[14]
|
|
||||||
}
|
|
||||||
var c = GridBagConstraints()
|
|
||||||
c.gridx=0; c.gridy=1; c.gridwidth=3
|
|
||||||
add(borderTop, c)
|
|
||||||
c = GridBagConstraints()
|
|
||||||
c.gridx=0; c.gridy=2
|
|
||||||
add(borderLeft, c)
|
|
||||||
c = GridBagConstraints()
|
|
||||||
c.gridx=2; c.gridy=2
|
|
||||||
add(borderRight, c)
|
|
||||||
c = GridBagConstraints()
|
|
||||||
c.gridx=0; c.gridy=3; c.gridwidth=3
|
|
||||||
add(borderBottom, c)
|
|
||||||
// the screen canvas(bitmap)
|
|
||||||
c = GridBagConstraints()
|
|
||||||
c.gridx = 1; c.gridy = 2
|
|
||||||
add(canvas, c)
|
|
||||||
|
|
||||||
canvas.requestFocusInWindow()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
val repaintTimer = Timer(1000 / 60) { repaint() }
|
|
||||||
repaintTimer.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun BufferedImage.copy(): BufferedImage {
|
|
||||||
val bcopy = BufferedImage(this.width, this.height, this.type)
|
|
||||||
val g = bcopy.graphics
|
|
||||||
g.drawImage(this, 0, 0, null)
|
|
||||||
g.dispose()
|
|
||||||
return bcopy
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package prog8.vm.astvm
|
|
||||||
|
|
||||||
import prog8.ast.Program
|
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.ast.base.Position
|
|
||||||
import prog8.ast.base.Register
|
|
||||||
import prog8.ast.base.VarDeclType
|
|
||||||
import prog8.ast.expressions.ArrayLiteralValue
|
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
|
||||||
import prog8.ast.processing.IAstModifyingVisitor
|
|
||||||
import prog8.ast.statements.Statement
|
|
||||||
import prog8.ast.statements.StructDecl
|
|
||||||
import prog8.ast.statements.VarDecl
|
|
||||||
import prog8.ast.statements.ZeropageWish
|
|
||||||
import prog8.vm.RuntimeValueArray
|
|
||||||
import prog8.vm.RuntimeValueNumeric
|
|
||||||
import prog8.vm.RuntimeValueString
|
|
||||||
|
|
||||||
class VariablesCreator(private val runtimeVariables: RuntimeVariables) : IAstModifyingVisitor {
|
|
||||||
|
|
||||||
override fun visit(program: Program) {
|
|
||||||
// define the three registers as global variables
|
|
||||||
runtimeVariables.define(program.namespace, Register.A.name, RuntimeValueNumeric(DataType.UBYTE, 0))
|
|
||||||
runtimeVariables.define(program.namespace, Register.X.name, RuntimeValueNumeric(DataType.UBYTE, 255))
|
|
||||||
runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValueNumeric(DataType.UBYTE, 0))
|
|
||||||
|
|
||||||
val globalpos = Position("<<global>>", 0, 0, 0)
|
|
||||||
val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.A.name, null,
|
|
||||||
NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
|
|
||||||
val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.X.name, null,
|
|
||||||
NumericLiteralValue.optimalInteger(255, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
|
|
||||||
val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.Y.name, null,
|
|
||||||
NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
|
|
||||||
vdA.linkParents(program.namespace)
|
|
||||||
vdX.linkParents(program.namespace)
|
|
||||||
vdY.linkParents(program.namespace)
|
|
||||||
program.namespace.statements.add(vdA)
|
|
||||||
program.namespace.statements.add(vdX)
|
|
||||||
program.namespace.statements.add(vdY)
|
|
||||||
|
|
||||||
super.visit(program)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(decl: VarDecl): Statement {
|
|
||||||
// if the decl is part of a struct, just skip it
|
|
||||||
if(decl.parent !is StructDecl) {
|
|
||||||
when (decl.type) {
|
|
||||||
VarDeclType.VAR -> {
|
|
||||||
if(decl.datatype!=DataType.STRUCT) {
|
|
||||||
val numericLv = decl.value as? NumericLiteralValue
|
|
||||||
val value = if(numericLv!=null) {
|
|
||||||
RuntimeValueNumeric.fromLv(numericLv)
|
|
||||||
} else {
|
|
||||||
val strLv = decl.value as? StringLiteralValue
|
|
||||||
val arrayLv = decl.value as? ArrayLiteralValue
|
|
||||||
when {
|
|
||||||
strLv!=null -> {
|
|
||||||
RuntimeValueString.fromLv(strLv)
|
|
||||||
}
|
|
||||||
arrayLv!=null -> {
|
|
||||||
RuntimeValueArray.fromLv(arrayLv)
|
|
||||||
}
|
|
||||||
else -> throw VmExecutionException("weird var type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
runtimeVariables.define(decl.definingScope(), decl.name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VarDeclType.MEMORY -> {
|
|
||||||
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as NumericLiteralValue).number.toInt())
|
|
||||||
}
|
|
||||||
VarDeclType.CONST -> {
|
|
||||||
// consts should have been const-folded away
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(decl)
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,6 +5,7 @@ import org.junit.jupiter.api.TestInstance
|
|||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.ArrayLiteralValue
|
import prog8.ast.expressions.ArrayLiteralValue
|
||||||
|
import prog8.ast.expressions.InferredTypes
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -83,8 +84,11 @@ class TestParserNumericLiteralValue {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testEqualsRef() {
|
fun testEqualsRef() {
|
||||||
assertEquals(StringLiteralValue("hello", dummyPos), StringLiteralValue("hello", dummyPos))
|
assertEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("hello", false, dummyPos))
|
||||||
assertNotEquals(StringLiteralValue("hello", dummyPos), StringLiteralValue("bye", dummyPos))
|
assertNotEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("bye", false, dummyPos))
|
||||||
|
assertEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", true, dummyPos))
|
||||||
|
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("bye", true, dummyPos))
|
||||||
|
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", false, dummyPos))
|
||||||
|
|
||||||
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
|
||||||
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
||||||
@ -93,9 +97,9 @@ class TestParserNumericLiteralValue {
|
|||||||
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
|
||||||
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
|
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
|
||||||
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
|
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
|
||||||
val lv1 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOne, lvTwo, lvThree), dummyPos)
|
val lv1 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOne, lvTwo, lvThree), dummyPos)
|
||||||
val lv2 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
|
val lv2 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
|
||||||
val lv3 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
|
val lv3 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
|
||||||
assertEquals(lv1, lv2)
|
assertEquals(lv1, lv2)
|
||||||
assertNotEquals(lv1, lv3)
|
assertNotEquals(lv1, lv3)
|
||||||
}
|
}
|
||||||
|
@ -1,352 +0,0 @@
|
|||||||
package prog8tests
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.TestInstance
|
|
||||||
import prog8.ast.base.DataType
|
|
||||||
import prog8.vm.RuntimeValueNumeric
|
|
||||||
import kotlin.test.*
|
|
||||||
|
|
||||||
|
|
||||||
private fun sameValueAndType(v1: RuntimeValueNumeric, v2: RuntimeValueNumeric): Boolean {
|
|
||||||
return v1.type==v2.type && v1==v2
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
class TestRuntimeValueNumeric {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testValueRanges() {
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).integerValue())
|
|
||||||
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 255).integerValue())
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UBYTE, -1)}
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UBYTE, 256)}
|
|
||||||
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 0).integerValue())
|
|
||||||
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -128).integerValue())
|
|
||||||
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 127).integerValue())
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.BYTE, -129)}
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.BYTE, 128)}
|
|
||||||
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).integerValue())
|
|
||||||
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 65535).integerValue())
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UWORD, -1)}
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UWORD, 65536)}
|
|
||||||
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 0).integerValue())
|
|
||||||
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32768).integerValue())
|
|
||||||
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32767).integerValue())
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.WORD, -32769)}
|
|
||||||
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.WORD, 32768)}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testTruthiness()
|
|
||||||
{
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.BYTE, 0).asBoolean)
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 0).asBoolean)
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.WORD, 0).asBoolean)
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.UWORD, 0).asBoolean)
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 0.0).asBoolean)
|
|
||||||
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.BYTE, 42).asBoolean)
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 42).asBoolean)
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.WORD, 42).asBoolean)
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.UWORD, 42).asBoolean)
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 42.0).asBoolean)
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.BYTE, -42).asBoolean)
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.WORD, -42).asBoolean)
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.FLOAT, -42.0).asBoolean)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testIdentity() {
|
|
||||||
val v = RuntimeValueNumeric(DataType.UWORD, 12345)
|
|
||||||
assertEquals(v, v)
|
|
||||||
assertFalse(v != v)
|
|
||||||
assertTrue(v<=v)
|
|
||||||
assertTrue(v>=v)
|
|
||||||
assertFalse(v<v)
|
|
||||||
assertFalse(v>v)
|
|
||||||
|
|
||||||
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100)))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testEqualsAndNotEquals() {
|
|
||||||
assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100))
|
|
||||||
assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 100))
|
|
||||||
assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 100))
|
|
||||||
assertEquals(RuntimeValueNumeric(DataType.UWORD, 254), RuntimeValueNumeric(DataType.UBYTE, 254))
|
|
||||||
assertEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12345))
|
|
||||||
assertEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12345))
|
|
||||||
assertEquals(RuntimeValueNumeric(DataType.FLOAT, 100.0), RuntimeValueNumeric(DataType.UBYTE, 100))
|
|
||||||
assertEquals(RuntimeValueNumeric(DataType.FLOAT, 22239.0), RuntimeValueNumeric(DataType.UWORD, 22239))
|
|
||||||
assertEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.99))
|
|
||||||
|
|
||||||
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 100)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 100)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 254), RuntimeValueNumeric(DataType.UBYTE, 254)))
|
|
||||||
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12345)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12345)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 100.0), RuntimeValueNumeric(DataType.UBYTE, 100)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 22239.0), RuntimeValueNumeric(DataType.UWORD, 22239)))
|
|
||||||
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.99)))
|
|
||||||
|
|
||||||
assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 101))
|
|
||||||
assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 101))
|
|
||||||
assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 101))
|
|
||||||
assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 245), RuntimeValueNumeric(DataType.UBYTE, 246))
|
|
||||||
assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12346))
|
|
||||||
assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12346))
|
|
||||||
assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UBYTE, 9))
|
|
||||||
assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UWORD, 9))
|
|
||||||
assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.0))
|
|
||||||
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 101)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 101)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 101)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 245), RuntimeValueNumeric(DataType.UBYTE, 246)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12346)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12346)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UBYTE, 9)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UWORD, 9)))
|
|
||||||
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.0)))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testGreaterThan(){
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) > RuntimeValueNumeric(DataType.UBYTE, 99))
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) > RuntimeValueNumeric(DataType.UWORD, 253))
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) > RuntimeValueNumeric(DataType.FLOAT, 99.9))
|
|
||||||
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) >= RuntimeValueNumeric(DataType.UBYTE, 100))
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) >= RuntimeValueNumeric(DataType.UWORD, 254))
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) >= RuntimeValueNumeric(DataType.FLOAT, 100.0))
|
|
||||||
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) > RuntimeValueNumeric(DataType.UBYTE, 100))
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) > RuntimeValueNumeric(DataType.UWORD, 254))
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) > RuntimeValueNumeric(DataType.FLOAT, 100.0))
|
|
||||||
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) >= RuntimeValueNumeric(DataType.UBYTE, 101))
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) >= RuntimeValueNumeric(DataType.UWORD, 255))
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) >= RuntimeValueNumeric(DataType.FLOAT, 100.1))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testLessThan() {
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) < RuntimeValueNumeric(DataType.UBYTE, 101))
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) < RuntimeValueNumeric(DataType.UWORD, 255))
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) < RuntimeValueNumeric(DataType.FLOAT, 100.1))
|
|
||||||
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) <= RuntimeValueNumeric(DataType.UBYTE, 100))
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) <= RuntimeValueNumeric(DataType.UWORD, 254))
|
|
||||||
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) <= RuntimeValueNumeric(DataType.FLOAT, 100.0))
|
|
||||||
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) < RuntimeValueNumeric(DataType.UBYTE, 100))
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) < RuntimeValueNumeric(DataType.UWORD, 254))
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) < RuntimeValueNumeric(DataType.FLOAT, 100.0))
|
|
||||||
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) <= RuntimeValueNumeric(DataType.UBYTE, 99))
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) <= RuntimeValueNumeric(DataType.UWORD, 253))
|
|
||||||
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) <= RuntimeValueNumeric(DataType.FLOAT, 99.9))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testNoDtConversion() {
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.UWORD, 100).add(RuntimeValueNumeric(DataType.UBYTE, 120))
|
|
||||||
}
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.UBYTE, 100).add(RuntimeValueNumeric(DataType.UWORD, 120))
|
|
||||||
}
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.FLOAT, 100.22).add(RuntimeValueNumeric(DataType.UWORD, 120))
|
|
||||||
}
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.UWORD, 1002).add(RuntimeValueNumeric(DataType.FLOAT, 120.22))
|
|
||||||
}
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.FLOAT, 100.22).add(RuntimeValueNumeric(DataType.UBYTE, 120))
|
|
||||||
}
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.UBYTE, 12).add(RuntimeValueNumeric(DataType.FLOAT, 120.22))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testNoAutoFloatConversion() {
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.UBYTE, 233).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
|
||||||
}
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.UWORD, 233).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
|
||||||
}
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.UBYTE, 233).mul(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
|
||||||
}
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.UWORD, 233).mul(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
|
||||||
}
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.UBYTE, 233).div(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
|
||||||
}
|
|
||||||
assertFailsWith<ArithmeticException> {
|
|
||||||
RuntimeValueNumeric(DataType.UWORD, 233).div(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
|
||||||
}
|
|
||||||
val result = RuntimeValueNumeric(DataType.FLOAT, 233.333).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun arithmetictestUbyte() {
|
|
||||||
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 55)).integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 56)).integerValue())
|
|
||||||
assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 57)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 1)).integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 2)).integerValue())
|
|
||||||
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 3)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 254).inc().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 255).inc().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 1).dec().integerValue())
|
|
||||||
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 0).dec().integerValue())
|
|
||||||
|
|
||||||
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 0).inv().integerValue())
|
|
||||||
assertEquals(0b00110011, RuntimeValueNumeric(DataType.UBYTE, 0b11001100).inv().integerValue())
|
|
||||||
// assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).neg().integerValue())
|
|
||||||
// assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).neg().integerValue())
|
|
||||||
assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 0).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 1).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 111).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 255).not().integerValue())
|
|
||||||
|
|
||||||
assertEquals(200, RuntimeValueNumeric(DataType.UBYTE, 20).mul(RuntimeValueNumeric(DataType.UBYTE, 10)).integerValue())
|
|
||||||
assertEquals(144, RuntimeValueNumeric(DataType.UBYTE, 20).mul(RuntimeValueNumeric(DataType.UBYTE, 20)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(25, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 2)).integerValue())
|
|
||||||
assertEquals(125, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 3)).integerValue())
|
|
||||||
assertEquals(113, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 4)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(100, RuntimeValueNumeric(DataType.UBYTE, 50).shl().integerValue())
|
|
||||||
assertEquals(200, RuntimeValueNumeric(DataType.UBYTE, 100).shl().integerValue())
|
|
||||||
assertEquals(144, RuntimeValueNumeric(DataType.UBYTE, 200).shl().integerValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun arithmetictestUWord() {
|
|
||||||
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5535)).integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5536)).integerValue())
|
|
||||||
assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5537)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 1)).integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 2)).integerValue())
|
|
||||||
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 3)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 65534).inc().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 65535).inc().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 1).dec().integerValue())
|
|
||||||
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 0).dec().integerValue())
|
|
||||||
|
|
||||||
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 0).inv().integerValue())
|
|
||||||
assertEquals(0b0011001101010101, RuntimeValueNumeric(DataType.UWORD, 0b1100110010101010).inv().integerValue())
|
|
||||||
// assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).neg().integerValue())
|
|
||||||
// assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).neg().integerValue())
|
|
||||||
assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 0).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 1).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 11111).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 65535).not().integerValue())
|
|
||||||
|
|
||||||
assertEquals(2000, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 10)).integerValue())
|
|
||||||
assertEquals(40000, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 200)).integerValue())
|
|
||||||
assertEquals(14464, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 400)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(15625, RuntimeValueNumeric(DataType.UWORD, 5).pow(RuntimeValueNumeric(DataType.UWORD, 6)).integerValue())
|
|
||||||
assertEquals(12589, RuntimeValueNumeric(DataType.UWORD, 5).pow(RuntimeValueNumeric(DataType.UWORD, 7)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(10000, RuntimeValueNumeric(DataType.UWORD, 5000).shl().integerValue())
|
|
||||||
assertEquals(60000, RuntimeValueNumeric(DataType.UWORD, 30000).shl().integerValue())
|
|
||||||
assertEquals(14464, RuntimeValueNumeric(DataType.UWORD, 40000).shl().integerValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun arithmetictestByte() {
|
|
||||||
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 100).add(RuntimeValueNumeric(DataType.BYTE, 27)).integerValue())
|
|
||||||
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, 100).add(RuntimeValueNumeric(DataType.BYTE, 28)).integerValue())
|
|
||||||
assertEquals(-127, RuntimeValueNumeric(DataType.BYTE, 100).add(RuntimeValueNumeric(DataType.BYTE, 29)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(1, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 1)).integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 2)).integerValue())
|
|
||||||
assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 3)).integerValue())
|
|
||||||
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -100).sub(RuntimeValueNumeric(DataType.BYTE, 28)).integerValue())
|
|
||||||
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, -100).sub(RuntimeValueNumeric(DataType.BYTE, 29)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 126).inc().integerValue())
|
|
||||||
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, 127).inc().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 1).dec().integerValue())
|
|
||||||
assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 0).dec().integerValue())
|
|
||||||
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -127).dec().integerValue())
|
|
||||||
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, -128).dec().integerValue())
|
|
||||||
|
|
||||||
assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 0).inv().integerValue())
|
|
||||||
assertEquals(-103, RuntimeValueNumeric(DataType.BYTE, 0b01100110).inv().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 0).neg().integerValue())
|
|
||||||
assertEquals(-2, RuntimeValueNumeric(DataType.BYTE, 2).neg().integerValue())
|
|
||||||
assertEquals(1, RuntimeValueNumeric(DataType.BYTE, 0).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 1).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 111).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, -33).not().integerValue())
|
|
||||||
|
|
||||||
assertEquals(100, RuntimeValueNumeric(DataType.BYTE, 10).mul(RuntimeValueNumeric(DataType.BYTE, 10)).integerValue())
|
|
||||||
assertEquals(-56, RuntimeValueNumeric(DataType.BYTE, 20).mul(RuntimeValueNumeric(DataType.BYTE, 10)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(25, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 2)).integerValue())
|
|
||||||
assertEquals(125, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 3)).integerValue())
|
|
||||||
assertEquals(113, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 4)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(100, RuntimeValueNumeric(DataType.BYTE, 50).shl().integerValue())
|
|
||||||
assertEquals(-56, RuntimeValueNumeric(DataType.BYTE, 100).shl().integerValue())
|
|
||||||
assertEquals(-2, RuntimeValueNumeric(DataType.BYTE, -1).shl().integerValue())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun arithmetictestWorrd() {
|
|
||||||
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32700).add(RuntimeValueNumeric(DataType.WORD, 67)).integerValue())
|
|
||||||
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, 32700).add(RuntimeValueNumeric(DataType.WORD, 68)).integerValue())
|
|
||||||
assertEquals(-32767, RuntimeValueNumeric(DataType.WORD, 32700).add(RuntimeValueNumeric(DataType.WORD, 69)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(1, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 1)).integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 2)).integerValue())
|
|
||||||
assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 3)).integerValue())
|
|
||||||
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32700).sub(RuntimeValueNumeric(DataType.WORD, 68)).integerValue())
|
|
||||||
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, -32700).sub(RuntimeValueNumeric(DataType.WORD, 69)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32766).inc().integerValue())
|
|
||||||
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, 32767).inc().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 1).dec().integerValue())
|
|
||||||
assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 0).dec().integerValue())
|
|
||||||
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32767).dec().integerValue())
|
|
||||||
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, -32768).dec().integerValue())
|
|
||||||
|
|
||||||
assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 0).inv().integerValue())
|
|
||||||
assertEquals(-103, RuntimeValueNumeric(DataType.WORD, 0b01100110).inv().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 0).neg().integerValue())
|
|
||||||
assertEquals(-2, RuntimeValueNumeric(DataType.WORD, 2).neg().integerValue())
|
|
||||||
assertEquals(1, RuntimeValueNumeric(DataType.WORD, 0).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 1).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 111).not().integerValue())
|
|
||||||
assertEquals(0, RuntimeValueNumeric(DataType.WORD, -33).not().integerValue())
|
|
||||||
|
|
||||||
assertEquals(10000, RuntimeValueNumeric(DataType.WORD, 100).mul(RuntimeValueNumeric(DataType.WORD, 100)).integerValue())
|
|
||||||
assertEquals(-25536, RuntimeValueNumeric(DataType.WORD, 200).mul(RuntimeValueNumeric(DataType.WORD, 200)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(15625, RuntimeValueNumeric(DataType.WORD, 5).pow(RuntimeValueNumeric(DataType.WORD, 6)).integerValue())
|
|
||||||
assertEquals(-6487, RuntimeValueNumeric(DataType.WORD, 9).pow(RuntimeValueNumeric(DataType.WORD, 5)).integerValue())
|
|
||||||
|
|
||||||
assertEquals(18000, RuntimeValueNumeric(DataType.WORD, 9000).shl().integerValue())
|
|
||||||
assertEquals(-25536, RuntimeValueNumeric(DataType.WORD, 20000).shl().integerValue())
|
|
||||||
assertEquals(-2, RuntimeValueNumeric(DataType.WORD, -1).shl().integerValue())
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,6 +6,7 @@ import org.hamcrest.Matchers.equalTo
|
|||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
import prog8.ast.base.DataType
|
import prog8.ast.base.DataType
|
||||||
|
import prog8.ast.base.ErrorReporter
|
||||||
import prog8.ast.base.Position
|
import prog8.ast.base.Position
|
||||||
import prog8.ast.expressions.NumericLiteralValue
|
import prog8.ast.expressions.NumericLiteralValue
|
||||||
import prog8.ast.expressions.StringLiteralValue
|
import prog8.ast.expressions.StringLiteralValue
|
||||||
@ -15,7 +16,6 @@ import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
|||||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||||
import prog8.compiler.target.c64.Petscii
|
import prog8.compiler.target.c64.Petscii
|
||||||
import prog8.vm.RuntimeValueNumeric
|
|
||||||
import java.io.CharConversionException
|
import java.io.CharConversionException
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
@ -124,31 +124,34 @@ class TestCompiler {
|
|||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
class TestZeropage {
|
class TestZeropage {
|
||||||
|
|
||||||
|
private val errors = ErrorReporter()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNames() {
|
fun testNames() {
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
|
||||||
|
|
||||||
zp.allocate("", DataType.UBYTE, null)
|
zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
zp.allocate("", DataType.UBYTE, null)
|
zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
zp.allocate("varname", DataType.UBYTE, null)
|
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||||
assertFailsWith<AssertionError> {
|
assertFailsWith<AssertionError> {
|
||||||
zp.allocate("varname", DataType.UBYTE, null)
|
zp.allocate("varname", DataType.UBYTE, null, errors)
|
||||||
}
|
}
|
||||||
zp.allocate("varname2", DataType.UBYTE, null)
|
zp.allocate("varname2", DataType.UBYTE, null, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testZpFloatEnable() {
|
fun testZpFloatEnable() {
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||||
assertFailsWith<CompilerException> {
|
assertFailsWith<CompilerException> {
|
||||||
zp.allocate("", DataType.FLOAT, null)
|
zp.allocate("", DataType.FLOAT, null, errors)
|
||||||
}
|
}
|
||||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
|
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
|
||||||
assertFailsWith<CompilerException> {
|
assertFailsWith<CompilerException> {
|
||||||
zp2.allocate("", DataType.FLOAT, null)
|
zp2.allocate("", DataType.FLOAT, null, errors)
|
||||||
}
|
}
|
||||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
||||||
zp3.allocate("", DataType.FLOAT, null)
|
zp3.allocate("", DataType.FLOAT, null, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -173,7 +176,7 @@ class TestZeropage {
|
|||||||
println(zp.free)
|
println(zp.free)
|
||||||
assertEquals(0, zp.available())
|
assertEquals(0, zp.available())
|
||||||
assertFailsWith<CompilerException> {
|
assertFailsWith<CompilerException> {
|
||||||
zp.allocate("", DataType.BYTE, null)
|
zp.allocate("", DataType.BYTE, null, errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,19 +221,19 @@ class TestZeropage {
|
|||||||
|
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
assertFailsWith<ZeropageDepletedError> {
|
||||||
// in regular zp there aren't 5 sequential bytes free
|
// in regular zp there aren't 5 sequential bytes free
|
||||||
zp.allocate("", DataType.FLOAT, null)
|
zp.allocate("", DataType.FLOAT, null, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i in 0 until zp.available()) {
|
for (i in 0 until zp.available()) {
|
||||||
val loc = zp.allocate("", DataType.UBYTE, null)
|
val loc = zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
assertTrue(loc > 0)
|
assertTrue(loc > 0)
|
||||||
}
|
}
|
||||||
assertEquals(0, zp.available())
|
assertEquals(0, zp.available())
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
assertFailsWith<ZeropageDepletedError> {
|
||||||
zp.allocate("", DataType.UBYTE, null)
|
zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
}
|
}
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
assertFailsWith<ZeropageDepletedError> {
|
||||||
zp.allocate("", DataType.UWORD, null)
|
zp.allocate("", DataType.UWORD, null, errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,29 +241,29 @@ class TestZeropage {
|
|||||||
fun testFullAllocation() {
|
fun testFullAllocation() {
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||||
assertEquals(238, zp.available())
|
assertEquals(238, zp.available())
|
||||||
val loc = zp.allocate("", DataType.UWORD, null)
|
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||||
assertTrue(loc > 3)
|
assertTrue(loc > 3)
|
||||||
assertFalse(loc in zp.free)
|
assertFalse(loc in zp.free)
|
||||||
val num = zp.available() / 2
|
val num = zp.available() / 2
|
||||||
|
|
||||||
for(i in 0..num-4) {
|
for(i in 0..num-4) {
|
||||||
zp.allocate("", DataType.UWORD, null)
|
zp.allocate("", DataType.UWORD, null, errors)
|
||||||
}
|
}
|
||||||
assertEquals(6,zp.available())
|
assertEquals(6,zp.available())
|
||||||
|
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
assertFailsWith<ZeropageDepletedError> {
|
||||||
// can't allocate because no more sequential bytes, only fragmented
|
// can't allocate because no more sequential bytes, only fragmented
|
||||||
zp.allocate("", DataType.UWORD, null)
|
zp.allocate("", DataType.UWORD, null, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
for(i in 0..5) {
|
for(i in 0..5) {
|
||||||
zp.allocate("", DataType.UBYTE, null)
|
zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(0, zp.available())
|
assertEquals(0, zp.available())
|
||||||
assertFailsWith<ZeropageDepletedError> {
|
assertFailsWith<ZeropageDepletedError> {
|
||||||
// no more space
|
// no more space
|
||||||
zp.allocate("", DataType.UBYTE, null)
|
zp.allocate("", DataType.UBYTE, null, errors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,16 +271,16 @@ class TestZeropage {
|
|||||||
fun testEfficientAllocation() {
|
fun testEfficientAllocation() {
|
||||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||||
assertEquals(16, zp.available())
|
assertEquals(16, zp.available())
|
||||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null))
|
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
||||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null))
|
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
||||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
|
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
|
||||||
assertEquals(0x94, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors))
|
||||||
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors))
|
||||||
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors))
|
||||||
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors))
|
||||||
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null))
|
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors))
|
||||||
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null))
|
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
|
||||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null))
|
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
|
||||||
assertEquals(0, zp.available())
|
assertEquals(0, zp.available())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -352,8 +355,8 @@ class TestPetscii {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testLiteralValueComparisons() {
|
fun testLiteralValueComparisons() {
|
||||||
val ten = NumericLiteralValue(DataType.UWORD, 10, Position("", 0, 0, 0))
|
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
|
||||||
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position("", 0, 0, 0))
|
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
|
||||||
assertEquals(ten, ten)
|
assertEquals(ten, ten)
|
||||||
assertNotEquals(ten, nine)
|
assertNotEquals(ten, nine)
|
||||||
assertFalse(ten != ten)
|
assertFalse(ten != ten)
|
||||||
@ -369,30 +372,10 @@ class TestPetscii {
|
|||||||
assertTrue(ten <= ten)
|
assertTrue(ten <= ten)
|
||||||
assertFalse(ten < ten)
|
assertFalse(ten < ten)
|
||||||
|
|
||||||
val abc = StringLiteralValue("abc", Position("", 0, 0, 0))
|
val abc = StringLiteralValue("abc", false, Position.DUMMY)
|
||||||
val abd = StringLiteralValue("abd", Position("", 0, 0, 0))
|
val abd = StringLiteralValue("abd", false, Position.DUMMY)
|
||||||
assertEquals(abc, abc)
|
assertEquals(abc, abc)
|
||||||
assertTrue(abc!=abd)
|
assertTrue(abc!=abd)
|
||||||
assertFalse(abc!=abc)
|
assertFalse(abc!=abc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testStackvmValueComparisons() {
|
|
||||||
val ten = RuntimeValueNumeric(DataType.FLOAT, 10)
|
|
||||||
val nine = RuntimeValueNumeric(DataType.UWORD, 9)
|
|
||||||
assertEquals(ten, ten)
|
|
||||||
assertNotEquals(ten, nine)
|
|
||||||
assertFalse(ten != ten)
|
|
||||||
assertTrue(ten != nine)
|
|
||||||
|
|
||||||
assertTrue(ten > nine)
|
|
||||||
assertTrue(ten >= nine)
|
|
||||||
assertTrue(ten >= ten)
|
|
||||||
assertFalse(ten > ten)
|
|
||||||
|
|
||||||
assertFalse(ten < nine)
|
|
||||||
assertFalse(ten <= nine)
|
|
||||||
assertTrue(ten <= ten)
|
|
||||||
assertFalse(ten < ten)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -168,18 +168,3 @@ or::
|
|||||||
|
|
||||||
$ ./p8compile.sh -emu examples/rasterbars.p8
|
$ ./p8compile.sh -emu examples/rasterbars.p8
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Virtual Machine / Simulator
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
You may have noticed the ``-sim`` command line option for the compiler:
|
|
||||||
|
|
||||||
-sim
|
|
||||||
Launches the "AST virtual machine Simulator" that directly executes the parsed program.
|
|
||||||
No compilation steps will be performed.
|
|
||||||
Allows for very fast testing and debugging before actually compiling programs
|
|
||||||
to machine code.
|
|
||||||
It simulates a bare minimum of features from the target platform, so most stuff
|
|
||||||
that calls ROM routines or writes into hardware registers won't work. But basic
|
|
||||||
system routines are emulated.
|
|
||||||
|
@ -135,30 +135,29 @@ Design principles and features
|
|||||||
|
|
||||||
- It is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...)
|
- 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.
|
The generated output is a machine code program runnable on actual 8-bit 6502 hardware.
|
||||||
- Usable on most operating systems.
|
- Based on simple and familiar imperative structured programming (it looks like a mix of C and Python)
|
||||||
- Based on simple and familiar imperative structured programming paradigm.
|
- 'One statement per line' code, resulting in clear readable programs.
|
||||||
- 'One statement per line' code style, resulting in clear readable programs.
|
|
||||||
- Modular programming and scoping via modules, code blocks, and subroutines.
|
- Modular programming and scoping via modules, code blocks, and subroutines.
|
||||||
- Provide high level programming constructs but stay close to the metal;
|
- Provide high level programming constructs but at the same time stay close to the metal;
|
||||||
still able to directly use memory addresses, CPU registers and ROM subroutines,
|
still able to directly use memory addresses and ROM subroutines,
|
||||||
and inline assembly to have full control when every cycle or byte matters
|
and inline assembly to have full control when every register, cycle or byte matters
|
||||||
- Arbitrary number of subroutine parameters (constrained only by available memory)
|
- Arbitrary number of subroutine parameters
|
||||||
- Complex nested expressions are possible
|
- Complex nested expressions are possible
|
||||||
- Values are typed. Types supported include signed and unsigned bytes and words, arrays, strings and floats.
|
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
|
||||||
|
- Values are typed. Available data types include signed and unsigned bytes and words, arrays, strings and floats.
|
||||||
- No dynamic memory allocation or sizing! All variables stay fixed size as determined at compile time.
|
- No dynamic memory allocation or sizing! All variables stay fixed size as determined at compile time.
|
||||||
- Provide various quality of life language features and library subroutines specifically for the target platform.
|
- Provide various quality of life language features and library subroutines specifically for the target platform.
|
||||||
- Provide a very convenient edit/compile/run cycle by being able to directly launch
|
- Provide a very convenient edit/compile/run cycle by being able to directly launch
|
||||||
the compiled program in an emulator and provide debugging information to the emulator.
|
the compiled program in an emulator and provide debugging information to this emulator.
|
||||||
- The compiler outputs a regular 6502 assembly source code file, but doesn't assemble this itself.
|
|
||||||
The (separate) '64tass' cross-assembler tool is used for that.
|
|
||||||
- Arbitrary control flow jumps and branches are possible,
|
- Arbitrary control flow jumps and branches are possible,
|
||||||
and will usually translate directly into the appropriate single 6502 jump/branch instruction.
|
and will usually translate directly into the appropriate single 6502 jump/branch instruction.
|
||||||
- There are no complicated built-in error handling or overflow checks, you'll have to take care
|
- There are no complicated built-in error handling or overflow checks, you'll have to take care
|
||||||
of this yourself if required. This keeps the language and code simple and efficient.
|
of this yourself if required. This keeps the language and code simple and efficient.
|
||||||
- The compiler tries to optimize the program and generated code, but hand-tuning of the
|
- The compiler tries to optimize the program and generated code a bit, but hand-tuning of the
|
||||||
performance or space-critical parts will likely still be required. This is supported by
|
performance or space-critical parts will likely still be required. This is supported by
|
||||||
the ability to easily write embedded assembly code directly in the program source code.
|
the ability to easily write embedded assembly code directly in the program source code.
|
||||||
- There are many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
- There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``substr``, ``sort`` and ``reverse`` (and others)
|
||||||
|
- Assembling the generated code into a program wil be done by an external cross-assembler tool.
|
||||||
|
|
||||||
|
|
||||||
.. _requirements:
|
.. _requirements:
|
||||||
@ -167,26 +166,25 @@ Required tools
|
|||||||
--------------
|
--------------
|
||||||
|
|
||||||
`64tass <https://sourceforge.net/projects/tass64/>`_ - cross assembler. Install this on your shell path.
|
`64tass <https://sourceforge.net/projects/tass64/>`_ - cross assembler. Install this on your shell path.
|
||||||
A recent .exe version of this tool for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
|
It's very easy to compile yourself.
|
||||||
For other platforms it is very easy to compile it yourself (make ; make install).
|
A recent precompiled .exe for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
|
||||||
|
|
||||||
A **Java runtime (jre or jdk), version 8 or newer** is required to run the packaged compiler.
|
A **Java runtime (jre or jdk), version 8 or newer** is required to run the prog8 compiler itself.
|
||||||
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead
|
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead.
|
||||||
and for Windows it's possible to get that as well. Check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ for
|
Fnd for Windows it's possible to get that as well. Check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
|
||||||
downloads.
|
|
||||||
|
|
||||||
Finally: a **C-64 emulator** (or a real C-64 ofcourse) to run the programs on. The compiler assumes the presence
|
Finally: a **C-64 emulator** (or a real C-64 ofcourse) can be nice to test and run your programs on.
|
||||||
of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
The compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||||
|
|
||||||
.. important::
|
.. important::
|
||||||
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
|
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
|
||||||
|
|
||||||
(re)building the compiler itself requires a recent Kotlin SDK.
|
(Re)building the compiler itself requires a recent Kotlin SDK.
|
||||||
The compiler is developed using the `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_
|
The compiler is developed using `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_ ,
|
||||||
IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available).
|
but only a Kotlin SDK installation should work as well, because the gradle tool is
|
||||||
But a bare Kotlin SDK installation should work just as well.
|
used to compile everything from the commandline.
|
||||||
|
|
||||||
Instructions on how to obtain a working compiler are in :ref:`building_compiler`.
|
Instructions on how to obtain a prebuilt compiler are in :ref:`building_compiler`.
|
||||||
|
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
|
@ -15,18 +15,13 @@ Program
|
|||||||
Consists of one or more *modules*.
|
Consists of one or more *modules*.
|
||||||
|
|
||||||
Module
|
Module
|
||||||
A file on disk with the ``.p8`` suffix. It contains *directives* and *code blocks*.
|
A file on disk with the ``.p8`` suffix. It can contain *directives* and *code blocks*.
|
||||||
Whitespace and indentation in the source code are arbitrary and can be tabs or spaces or both.
|
Whitespace and indentation in the source code are arbitrary and can be mixed tabs or spaces.
|
||||||
You can also add *comments* to the source code.
|
A module file can *import* other modules, including *library modules*.
|
||||||
One moudule file can *import* others, and also import *library modules*.
|
|
||||||
|
|
||||||
Comments
|
Comments
|
||||||
Everything after a semicolon ``;`` is a comment and is ignored by the compiler.
|
Everything after a semicolon ``;`` is a comment and is ignored by the compiler.
|
||||||
If the whole line is just a comment, it will be copied into the resulting assembly source code.
|
If the whole line is just a comment, this line will be copied into the resulting assembly source code for reference.
|
||||||
This makes it easier to understand and relate the generated code. Examples::
|
|
||||||
|
|
||||||
A = 42 ; set the initial value to 42
|
|
||||||
; next is the code that...
|
|
||||||
|
|
||||||
Directive
|
Directive
|
||||||
These are special instructions for the compiler, to change how it processes the code
|
These are special instructions for the compiler, to change how it processes the code
|
||||||
@ -34,8 +29,9 @@ Directive
|
|||||||
starts with ``%``, optionally followed by some arguments.
|
starts with ``%``, optionally followed by some arguments.
|
||||||
|
|
||||||
Code block
|
Code block
|
||||||
A block of actual program code. It defines a *scope* (also known as 'namespace') and
|
A block of actual program code. It has a starting address in memory,
|
||||||
can contain Prog8 *code*, *variable declarations* and *subroutines*.
|
and defines a *scope* (also known as 'namespace').
|
||||||
|
It contains variables and subroutines.
|
||||||
More details about this below: :ref:`blocks`.
|
More details about this below: :ref:`blocks`.
|
||||||
|
|
||||||
Variable declarations
|
Variable declarations
|
||||||
@ -49,11 +45,12 @@ Variable declarations
|
|||||||
that don't allocate storage but instead point to a fixed location in the address space.
|
that don't allocate storage but instead point to a fixed location in the address space.
|
||||||
|
|
||||||
Code
|
Code
|
||||||
These are the instructions that make up the program's logic. There are different kinds of instructions
|
These are the instructions that make up the program's logic.
|
||||||
('statements' is a better name):
|
Code can only occur inside a subroutine.
|
||||||
|
There are different kinds of instructions ('statements' is a better name) such as:
|
||||||
|
|
||||||
- value assignment
|
- value assignment
|
||||||
- looping (for, while, repeat, unconditional jumps)
|
- looping (for, while, do-until, repeat, unconditional jumps)
|
||||||
- conditional execution (if - then - else, when, and conditional jumps)
|
- conditional execution (if - then - else, when, and conditional jumps)
|
||||||
- subroutine calls
|
- subroutine calls
|
||||||
- label definition
|
- label definition
|
||||||
@ -63,6 +60,8 @@ Subroutine
|
|||||||
It accepts parameters and can return a value (optional).
|
It accepts parameters and can return a value (optional).
|
||||||
It can define its own variables, and it is even possible to define subroutines nested inside other subroutines.
|
It can define its own variables, and it is even possible to define subroutines nested inside other subroutines.
|
||||||
Their contents is scoped accordingly.
|
Their contents is scoped accordingly.
|
||||||
|
Nested subroutines can access the variables from outer scopes.
|
||||||
|
This removes the need and overhead to pass everything via parameters.
|
||||||
|
|
||||||
Label
|
Label
|
||||||
This is a named position in your code where you can jump to from another place.
|
This is a named position in your code where you can jump to from another place.
|
||||||
@ -90,16 +89,20 @@ Scope
|
|||||||
Blocks, Scopes, and accessing Symbols
|
Blocks, Scopes, and accessing Symbols
|
||||||
-------------------------------------
|
-------------------------------------
|
||||||
|
|
||||||
**Blocks** are the top level separate pieces of code and data of your program. They are combined
|
**Blocks** are the top level separate pieces of code and data of your program. They have a
|
||||||
into a single output program. No code or data can occur outside a block. Here's an example::
|
starting address in memory and will be combined together into a single output program.
|
||||||
|
They can only contain *directives*, *variable declarations*, *subroutines* and *inline assembly code*.
|
||||||
|
Your actual program code can only exist inside these subroutines.
|
||||||
|
(except the occasional inline assembly)
|
||||||
|
|
||||||
|
Here's an example::
|
||||||
|
|
||||||
main $c000 {
|
main $c000 {
|
||||||
; this is code inside the block...
|
; this is code inside the block...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
The name of a block must be unique in your entire program.
|
The name of a block must be unique in your entire program.
|
||||||
Also be careful when importing other modules; blocks in your own code cannot have
|
Be careful when importing other modules; blocks in your own code cannot have
|
||||||
the same name as a block defined in an imported module or library.
|
the same name as a block defined in an imported module or library.
|
||||||
|
|
||||||
If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed).
|
If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed).
|
||||||
@ -109,7 +112,7 @@ want to work on later, because the contents of the ignored block are not fully p
|
|||||||
The address can be used to place a block at a specific location in memory.
|
The address can be used to place a block at a specific location in memory.
|
||||||
Usually it is omitted, and the compiler will automatically choose the location (usually immediately after
|
Usually it is omitted, and the compiler will automatically choose the location (usually immediately after
|
||||||
the previous block in memory).
|
the previous block in memory).
|
||||||
The address must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$200`` is the cpu stack).
|
It must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$1ff`` is the cpu stack).
|
||||||
|
|
||||||
|
|
||||||
.. _scopes:
|
.. _scopes:
|
||||||
@ -132,15 +135,18 @@ Scopes are created using either of these two statements:
|
|||||||
- blocks (top-level named scope)
|
- blocks (top-level named scope)
|
||||||
- subroutines (nested named scope)
|
- subroutines (nested named scope)
|
||||||
|
|
||||||
.. note::
|
.. important::
|
||||||
In contrast to many other programming languages, a new scope is *not* created inside
|
Unlike most other programming languages, a new scope is *not* created inside
|
||||||
for, while and repeat statements, nor for the if statement and branching conditionals.
|
for, while, repeat, and do-until statements, the if statement, and the branching conditionals.
|
||||||
This is a bit restrictive because you have to think harder about what variables you
|
These all share the same scope from the subroutine they're defined in.
|
||||||
want to use inside a subroutine. But it is done precisely for this reason; memory in the
|
You can define variables in these blocks, but these will be treated as if they
|
||||||
target system is very limited and it would be a waste to allocate a lot of variables.
|
were defined in the subroutine instead.
|
||||||
|
This can seem a bit restrictive because you have to think harder about what variables you
|
||||||
Right now the prog8 compiler is not advanced enough to be able to 'share' or 'overlap'
|
want to use inside the subroutine, to avoid clashes.
|
||||||
variables intelligently by itself. So for now, it's something the programmer has to think about.
|
But this decision was made for a good reason: memory in prog8's
|
||||||
|
target systems is usually very limited and it would be a waste to allocate a lot of variables.
|
||||||
|
The prog8 compiler is not yet advanced enough to be able to share or overlap
|
||||||
|
variables intelligently. So for now that is something you have to think about yourself.
|
||||||
|
|
||||||
|
|
||||||
Program Start and Entry Point
|
Program Start and Entry Point
|
||||||
@ -150,13 +156,6 @@ Your program must have a single entry point where code execution begins.
|
|||||||
The compiler expects a ``start`` subroutine in the ``main`` block for this,
|
The compiler expects a ``start`` subroutine in the ``main`` block for this,
|
||||||
taking no parameters and having no return value.
|
taking no parameters and having no return value.
|
||||||
|
|
||||||
.. sidebar::
|
|
||||||
60hz IRQ entry point
|
|
||||||
|
|
||||||
When running the generated code on the StackVm virtual machine,
|
|
||||||
it will use the ``irq`` subroutine in the ``irq`` block for the
|
|
||||||
60hz irq routine. This is optional.
|
|
||||||
|
|
||||||
As any subroutine, it has to end with a ``return`` statement (or a ``goto`` call)::
|
As any subroutine, it has to end with a ``return`` statement (or a ``goto`` call)::
|
||||||
|
|
||||||
main {
|
main {
|
||||||
@ -175,12 +174,11 @@ calls with the SYS statement.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Variables and values
|
Variables and values
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
Variables are named values that can change during the execution of the program.
|
Variables are named values that can change during the execution of the program.
|
||||||
They can be defined inside any scope (blocks, subroutines, for loops, etc.) See :ref:`Scopes <scopes>`.
|
They can be defined inside any scope (blocks, subroutines etc.) See :ref:`Scopes <scopes>`.
|
||||||
When declaring a numeric variable it is possible to specify the initial value, if you don't want it to be zero.
|
When declaring a numeric variable it is possible to specify the initial value, if you don't want it to be zero.
|
||||||
For other data types it is required to specify that initial value it should get.
|
For other data types it is required to specify that initial value it should get.
|
||||||
Values will usually be part of an expression or assignment statement::
|
Values will usually be part of an expression or assignment statement::
|
||||||
@ -188,9 +186,11 @@ Values will usually be part of an expression or assignment statement::
|
|||||||
12345 ; integer number
|
12345 ; integer number
|
||||||
$aa43 ; hex integer number
|
$aa43 ; hex integer number
|
||||||
%100101 ; binary integer number (% is also remainder operator so be careful)
|
%100101 ; binary integer number (% is also remainder operator so be careful)
|
||||||
"Hi, I am a string" ; text string
|
|
||||||
'a' ; petscii value (byte) for the letter a
|
|
||||||
-33.456e52 ; floating point number
|
-33.456e52 ; floating point number
|
||||||
|
"Hi, I am a string" ; text string, encoded with compiler target default encoding
|
||||||
|
'a' ; byte value (ubyte) for the letter a
|
||||||
|
@"Alternate" ; text string, encoded with alternate encoding
|
||||||
|
@'a' ; byte value of the letter a, using alternate encoding
|
||||||
|
|
||||||
byte counter = 42 ; variable of size 8 bits, with initial value 42
|
byte counter = 42 ; variable of size 8 bits, with initial value 42
|
||||||
|
|
||||||
@ -204,13 +204,6 @@ Example::
|
|||||||
byte @zp zeropageCounter = 42
|
byte @zp zeropageCounter = 42
|
||||||
|
|
||||||
|
|
||||||
Variables that represent CPU hardware registers
|
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
The following variables are reserved
|
|
||||||
and map directly (read/write) to a CPU hardware register: ``A``, ``X``, ``Y``.
|
|
||||||
|
|
||||||
|
|
||||||
Integers
|
Integers
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|
||||||
@ -271,20 +264,31 @@ Strings
|
|||||||
Strings are a sequence of characters enclosed in ``"`` quotes. The length is limited to 255 characters.
|
Strings are a sequence of characters enclosed in ``"`` quotes. The length is limited to 255 characters.
|
||||||
They're stored and treated much the same as a byte array,
|
They're stored and treated much the same as a byte array,
|
||||||
but they have some special properties because they are considered to be *text*.
|
but they have some special properties because they are considered to be *text*.
|
||||||
Strings in your source code files will be encoded (translated from ASCII/UTF-8) into the byte-encoding
|
Strings in your source code files will be encoded (translated from ASCII/UTF-8) into bytes via the
|
||||||
that is used on the target platform. For the C-64, this is CBM PETSCII.
|
default encoding that is used on the target platform. For the C-64, this is CBM PETSCII.
|
||||||
|
Alternate-encoding strings (prefixed with ``@``) will be encoded via the alternate encoding for the
|
||||||
|
platform (if defined). For the C-64, that is SCREEN CODES (also known as POKE codes).
|
||||||
|
This @-prefix can also be used for character byte values.
|
||||||
|
|
||||||
|
|
||||||
You can concatenate two string literals using '+' (not very useful though) or repeat
|
You can concatenate two string literals using '+' (not very useful though) or repeat
|
||||||
a string literal a given number of times using '*'::
|
a string literal a given number of times using '*'. You can also assign a new string
|
||||||
|
value to another string. No bounds check is done so be sure the destination string is
|
||||||
|
large enough to contain the new value::
|
||||||
|
|
||||||
str string1 = "first part" + "second part"
|
str string1 = "first part" + "second part"
|
||||||
str string2 = "hello!" * 10
|
str string2 = "hello!" * 10
|
||||||
|
|
||||||
|
string1 = string2
|
||||||
|
string1 = "new value"
|
||||||
|
|
||||||
|
|
||||||
.. caution::
|
.. caution::
|
||||||
Avoid changing strings after they've been created.
|
It's probably best to avoid changing strings after they've been created. This
|
||||||
|
includes changing certain letters by index, or by assigning a new value, or by
|
||||||
|
modifying the string via other means for example ``substr`` function and its cousins.
|
||||||
This is because if your program exits and is restarted (without loading it again),
|
This is because if your program exits and is restarted (without loading it again),
|
||||||
it will then start working with the changed strings instead of the original ones.
|
it will then start working with the changed strings instead of the original ones!
|
||||||
The same is true for arrays.
|
The same is true for arrays.
|
||||||
|
|
||||||
|
|
||||||
@ -368,10 +372,8 @@ Initial values across multiple runs of the program
|
|||||||
|
|
||||||
When declaring values with an initial value, this value will be set into the variable each time
|
When declaring values with an initial value, this value will be set into the variable each time
|
||||||
the program reaches the declaration again. This can be in loops, multiple subroutine calls,
|
the program reaches the declaration again. This can be in loops, multiple subroutine calls,
|
||||||
or even multiple invocations of the entire program. If you omit an initial value, it will
|
or even multiple invocations of the entire program.
|
||||||
be set to zero *but only for the first run of the program*. A second run will utilize the last value
|
If you omit the initial value, zero will be used instead.
|
||||||
where it left off (but your code will be a bit smaller because no initialization instructions
|
|
||||||
are generated)
|
|
||||||
|
|
||||||
This only works for simple types, *and not for string variables and arrays*.
|
This only works for simple types, *and not for string variables and arrays*.
|
||||||
It is assumed these are left unchanged by the program; they are not re-initialized on
|
It is assumed these are left unchanged by the program; they are not re-initialized on
|
||||||
@ -380,30 +382,23 @@ If you do modify them in-place, you should take care yourself that they work as
|
|||||||
expected when the program is restarted.
|
expected when the program is restarted.
|
||||||
(This is an optimization choice to avoid having to store two copies of every string and array)
|
(This is an optimization choice to avoid having to store two copies of every string and array)
|
||||||
|
|
||||||
.. caution::
|
|
||||||
variables that get allocated in zero-page will *not* have a zero starting value when you omit
|
|
||||||
the variable's initialization. They'll be whatever the last value in that zero page
|
|
||||||
location was. So it's best to don't depend on the uninitialized starting value!
|
|
||||||
|
|
||||||
.. warning::
|
|
||||||
this behavior may change in a future version so that subsequent runs always
|
|
||||||
use the same initial values
|
|
||||||
|
|
||||||
|
|
||||||
Loops
|
Loops
|
||||||
-----
|
-----
|
||||||
|
|
||||||
The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
The *for*-loop is used to let a variable iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
||||||
The loop variable must be declared as byte or word earlier so you can reuse it for multiple occasions.
|
The loop variable must be declared as byte or word earlier so you can reuse it for multiple occasions.
|
||||||
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
|
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
|
||||||
|
|
||||||
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
|
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
|
||||||
The *repeat--until* loop is used to repeat a piece of code until a certain condition is true.
|
The *do--until* loop is used to repeat a piece of code until a certain condition is true.
|
||||||
|
The *repeat* loop is used as a short notation of a for loop where the loop variable doesn't matter and you're only interested in the number of iterations.
|
||||||
|
(without iteration count specified it simply loops forever).
|
||||||
|
|
||||||
You can also create loops by using the ``goto`` statement, but this should usually be avoided.
|
You can also create loops by using the ``goto`` statement, but this should usually be avoided.
|
||||||
|
|
||||||
.. attention::
|
.. attention::
|
||||||
The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately
|
The value of the loop variable after executing the loop *is undefined*. Don't use it immediately
|
||||||
after the loop without first assigning a new value to it!
|
after the loop without first assigning a new value to it!
|
||||||
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
|
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
|
||||||
|
|
||||||
@ -417,15 +412,15 @@ if statements
|
|||||||
Conditional execution means that the flow of execution changes based on certiain conditions,
|
Conditional execution means that the flow of execution changes based on certiain conditions,
|
||||||
rather than having fixed gotos or subroutine calls::
|
rather than having fixed gotos or subroutine calls::
|
||||||
|
|
||||||
if A>4 goto overflow
|
if aa>4 goto overflow
|
||||||
|
|
||||||
if X==3 Y = 4
|
if xx==3 yy = 4
|
||||||
if X==3 Y = 4 else A = 2
|
if xx==3 yy = 4 else aa = 2
|
||||||
|
|
||||||
if X==5 {
|
if xx==5 {
|
||||||
Y = 99
|
yy = 99
|
||||||
} else {
|
} else {
|
||||||
A = 3
|
aa = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -489,16 +484,16 @@ Assignments
|
|||||||
-----------
|
-----------
|
||||||
|
|
||||||
Assignment statements assign a single value to a target variable or memory location.
|
Assignment statements assign a single value to a target variable or memory location.
|
||||||
Augmented assignments (such as ``A += X``) are also available, but these are just shorthands
|
Augmented assignments (such as ``aa += xx``) are also available, but these are just shorthands
|
||||||
for normal assignments (``A = A + X``).
|
for normal assignments (``aa = aa + xx``).
|
||||||
|
|
||||||
Only register variables and variables of type byte, word and float can be assigned a new value.
|
Only variables of type byte, word and float can be assigned a new value.
|
||||||
It's not possible to set a new value to string or array variables etc, because they get allocated
|
It's not possible to set a new value to string or array variables etc, because they get allocated
|
||||||
a fixed amount of memory which will not change.
|
a fixed amount of memory which will not change. (You *can* change the value of elements in a string or array though).
|
||||||
|
|
||||||
.. attention::
|
.. attention::
|
||||||
**Data type conversion (in assignments):**
|
**Data type conversion (in assignments):**
|
||||||
When assigning a value with a 'smaller' datatype to a register or variable with a 'larger' datatype,
|
When assigning a value with a 'smaller' datatype to variable with a 'larger' datatype,
|
||||||
the value will be automatically converted to the target datatype: byte --> word --> float.
|
the value will be automatically converted to the target datatype: byte --> word --> float.
|
||||||
So assigning a byte to a word variable, or a word to a floating point variable, is fine.
|
So assigning a byte to a word variable, or a word to a floating point variable, is fine.
|
||||||
The reverse is *not* true: it is *not* possible to assign a value of a 'larger' datatype to
|
The reverse is *not* true: it is *not* possible to assign a value of a 'larger' datatype to
|
||||||
@ -514,7 +509,7 @@ as the memory mapped address $d021.
|
|||||||
If you want to access a memory location directly (by using the address itself), without defining
|
If you want to access a memory location directly (by using the address itself), without defining
|
||||||
a memory mapped location, you can do so by enclosing the address in ``@(...)``::
|
a memory mapped location, you can do so by enclosing the address in ``@(...)``::
|
||||||
|
|
||||||
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)")
|
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
|
||||||
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
||||||
@(vic+$20) = 6 ; you can also use expressions to 'calculate' the address
|
@(vic+$20) = 6 ; you can also use expressions to 'calculate' the address
|
||||||
|
|
||||||
@ -707,9 +702,13 @@ sum(x)
|
|||||||
|
|
||||||
sort(array)
|
sort(array)
|
||||||
Sort the array in ascending order (in-place)
|
Sort the array in ascending order (in-place)
|
||||||
|
Note: sorting a floating-point array is not supported right now, as a general sorting routine for this will
|
||||||
|
be extremely slow. Either build one yourself or find another solution that doesn't require sorting
|
||||||
|
floating point values.
|
||||||
|
|
||||||
reverse(array)
|
reverse(array)
|
||||||
Reverse the values in the array (in-place). Can be used after sort() to sort an array in descending order.
|
Reverse the values in the array (in-place). Supports all data types including floats.
|
||||||
|
Can be used after sort() to sort an array in descending order.
|
||||||
|
|
||||||
len(x)
|
len(x)
|
||||||
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
|
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
|
||||||
@ -801,6 +800,22 @@ memsetw(address, numwords, wordvalue)
|
|||||||
Efficiently set a part of memory to the given (u)word value.
|
Efficiently set a part of memory to the given (u)word value.
|
||||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||||
|
|
||||||
|
leftstr(source, target, length)
|
||||||
|
Copies the left side of the source string of the given length to target string.
|
||||||
|
It is assumed the target string buffer is large enough to contain the result.
|
||||||
|
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||||
|
|
||||||
|
rightstr(source, target, length)
|
||||||
|
Copies the right side of the source string of the given length to target string.
|
||||||
|
It is assumed the target string buffer is large enough to contain the result.
|
||||||
|
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||||
|
|
||||||
|
substr(source, target, start, length)
|
||||||
|
Copies a segment from the source string, starting at the given index,
|
||||||
|
and of the given length to target string.
|
||||||
|
It is assumed the target string buffer is large enough to contain the result.
|
||||||
|
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||||
|
|
||||||
swap(x, y)
|
swap(x, y)
|
||||||
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
||||||
|
|
||||||
@ -826,6 +841,10 @@ rrestore()
|
|||||||
read_flags()
|
read_flags()
|
||||||
Returns the current value of the CPU status register.
|
Returns the current value of the CPU status register.
|
||||||
|
|
||||||
|
exit(returncode)
|
||||||
|
Immediately stops the program and exits it, with the returncode in the A register.
|
||||||
|
Note: custom interrupt handlers remain active unless manually cleared first!
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Library routines
|
Library routines
|
||||||
|
@ -24,7 +24,7 @@ Everything after a semicolon ``;`` is a comment and is ignored.
|
|||||||
If the whole line is just a comment, it will be copied into the resulting assembly source code.
|
If the whole line is just a comment, it will be copied into the resulting assembly source code.
|
||||||
This makes it easier to understand and relate the generated code. Examples::
|
This makes it easier to understand and relate the generated code. Examples::
|
||||||
|
|
||||||
A = 42 ; set the initial value to 42
|
counter = 42 ; set the initial value to 42
|
||||||
; next is the code that...
|
; next is the code that...
|
||||||
|
|
||||||
|
|
||||||
@ -172,13 +172,13 @@ Code blocks
|
|||||||
-----------
|
-----------
|
||||||
|
|
||||||
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
|
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
|
||||||
can contain Prog8 *code*, *directives*, *variable declarations* and *subroutines*::
|
can only contain *directives*, *variable declarations*, *subroutines* or *inline assembly*::
|
||||||
|
|
||||||
<blockname> [<address>] {
|
<blockname> [<address>] {
|
||||||
<directives>
|
<directives>
|
||||||
<variables>
|
<variables>
|
||||||
<statements>
|
|
||||||
<subroutines>
|
<subroutines>
|
||||||
|
<inline asm>
|
||||||
}
|
}
|
||||||
|
|
||||||
The <blockname> must be a valid identifier.
|
The <blockname> must be a valid identifier.
|
||||||
@ -191,7 +191,6 @@ Also read :ref:`blocks`. Here is an example of a code block, to be loaded at ``
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Labels
|
Labels
|
||||||
------
|
------
|
||||||
|
|
||||||
@ -217,7 +216,8 @@ Variable declarations
|
|||||||
|
|
||||||
Variables should be declared with their exact type and size so the compiler can allocate storage
|
Variables should be declared with their exact type and size so the compiler can allocate storage
|
||||||
for them. You can give them an initial value as well. That value can be a simple literal value,
|
for them. You can give them an initial value as well. That value can be a simple literal value,
|
||||||
or an expression. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
|
or an expression. If you don't provide an intial value yourself, zero will be used.
|
||||||
|
You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
|
||||||
when selecting variables to be put into zeropage.
|
when selecting variables to be put into zeropage.
|
||||||
The syntax is::
|
The syntax is::
|
||||||
|
|
||||||
@ -230,6 +230,7 @@ Various examples::
|
|||||||
byte age = 2018 - 1974
|
byte age = 2018 - 1974
|
||||||
float wallet = 55.25
|
float wallet = 55.25
|
||||||
str name = "my name is Irmen"
|
str name = "my name is Irmen"
|
||||||
|
str name = @"my name is Irmen" ; string with alternative byte encoding
|
||||||
uword address = &counter
|
uword address = &counter
|
||||||
byte[] values = [11, 22, 33, 44, 55]
|
byte[] values = [11, 22, 33, 44, 55]
|
||||||
byte[5] values ; array of 5 bytes, initially set to zero
|
byte[5] values ; array of 5 bytes, initially set to zero
|
||||||
@ -248,7 +249,7 @@ Prog8 supports the following data types:
|
|||||||
type identifier type storage size example var declaration and literal value
|
type identifier type storage size example var declaration and literal value
|
||||||
=============== ======================= ================= =========================================
|
=============== ======================= ================= =========================================
|
||||||
``byte`` signed byte 1 byte = 8 bits ``byte myvar = -22``
|
``byte`` signed byte 1 byte = 8 bits ``byte myvar = -22``
|
||||||
``ubyte`` unsigned byte 1 byte = 8 bits ``ubyte myvar = $8f``
|
``ubyte`` unsigned byte 1 byte = 8 bits ``ubyte myvar = $8f``, ``ubyte c = 'a'``, ``ubyte c2 = @'a'``
|
||||||
-- boolean 1 byte = 8 bits ``byte myvar = true`` or ``byte myvar == false``
|
-- boolean 1 byte = 8 bits ``byte myvar = true`` or ``byte myvar == false``
|
||||||
The true and false are actually just aliases
|
The true and false are actually just aliases
|
||||||
for the byte values 1 and 0.
|
for the byte values 1 and 0.
|
||||||
@ -312,7 +313,7 @@ Direct access to memory locations
|
|||||||
Instead of defining a memory mapped name for a specific memory location, you can also
|
Instead of defining a memory mapped name for a specific memory location, you can also
|
||||||
directly access the memory. Enclose a numeric expression or literal with ``@(...)`` to do that::
|
directly access the memory. Enclose a numeric expression or literal with ``@(...)`` to do that::
|
||||||
|
|
||||||
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)")
|
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
|
||||||
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
||||||
@(vic+$20) = 6 ; a dynamic expression to 'calculate' the address
|
@(vic+$20) = 6 ; a dynamic expression to 'calculate' the address
|
||||||
|
|
||||||
@ -321,7 +322,7 @@ Constants
|
|||||||
^^^^^^^^^
|
^^^^^^^^^
|
||||||
|
|
||||||
All variables can be assigned new values unless you use the ``const`` keyword.
|
All variables can be assigned new values unless you use the ``const`` keyword.
|
||||||
The initial value will now be evaluated at compile time (it must be a compile time constant expression).
|
The initial value must be known at compile time (it must be a compile time constant expression).
|
||||||
This is only valid for the simple numeric types (byte, word, float)::
|
This is only valid for the simple numeric types (byte, word, float)::
|
||||||
|
|
||||||
const byte max_age = 99
|
const byte max_age = 99
|
||||||
@ -332,17 +333,22 @@ Reserved names
|
|||||||
|
|
||||||
The following names are reserved, they have a special meaning::
|
The following names are reserved, they have a special meaning::
|
||||||
|
|
||||||
A X Y ; 6502 hardware registers
|
|
||||||
Pc Pz Pn Pv ; 6502 status register flags
|
|
||||||
true false ; boolean values 1 and 0
|
true false ; boolean values 1 and 0
|
||||||
|
|
||||||
|
|
||||||
Range expression
|
Range expression
|
||||||
^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
A special value is the *range expression* ( ``<startvalue> to <endvalue>`` )
|
A special value is the *range expression* which represents a range of numbers or characters,
|
||||||
which represents a range of numbers or characters,
|
from the starting value to (and including) the ending value::
|
||||||
from the starting value to (and including) the ending value.
|
|
||||||
|
<start> to <end> [ step <step> ]
|
||||||
|
<start> downto <end> [ step <step> ]
|
||||||
|
|
||||||
|
You an provide a step value if you need something else than the default increment which is one (or,
|
||||||
|
in case of downto, a decrement of one). Because a step of minus one is so common you can just use
|
||||||
|
the downto variant to avoid having to specify the step as well.
|
||||||
|
|
||||||
If used in the place of a literal value, it expands into the actual array of values::
|
If used in the place of a literal value, it expands into the actual array of values::
|
||||||
|
|
||||||
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||||
@ -398,10 +404,10 @@ assignment: ``=``
|
|||||||
Note that an assignment sometimes is not possible or supported.
|
Note that an assignment sometimes is not possible or supported.
|
||||||
|
|
||||||
augmented assignment: ``+=`` ``-=`` ``*=`` ``/=`` ``**=`` ``&=`` ``|=`` ``^=`` ``<<=`` ``>>=``
|
augmented assignment: ``+=`` ``-=`` ``*=`` ``/=`` ``**=`` ``&=`` ``|=`` ``^=`` ``<<=`` ``>>=``
|
||||||
Syntactic sugar; ``A += X`` is equivalent to ``A = A + X``
|
Syntactic sugar; ``aa += xx`` is equivalent to ``aa = aa + xx``
|
||||||
|
|
||||||
postfix increment and decrement: ``++`` ``--``
|
postfix increment and decrement: ``++`` ``--``
|
||||||
Syntactic sugar; ``A++`` is equivalent to ``A = A + 1``, and ``A--`` is equivalent to ``A = A - 1``.
|
Syntactic sugar; ``aa++`` is equivalent to ``aa = aa + 1``, and ``aa--`` is equivalent to ``aa = aa - 1``.
|
||||||
Because these operations are so common, we have these short forms.
|
Because these operations are so common, we have these short forms.
|
||||||
|
|
||||||
comparison: ``!=`` ``<`` ``>`` ``<=`` ``>=``
|
comparison: ``!=`` ``<`` ``>`` ``<=`` ``>=``
|
||||||
@ -419,9 +425,9 @@ range creation: ``to``
|
|||||||
|
|
||||||
0 to 7 ; range of values 0, 1, 2, 3, 4, 5, 6, 7 (constant)
|
0 to 7 ; range of values 0, 1, 2, 3, 4, 5, 6, 7 (constant)
|
||||||
|
|
||||||
A = 5
|
aa = 5
|
||||||
X = 10
|
aa = 10
|
||||||
A to X ; range of 5, 6, 7, 8, 9, 10
|
aa to xx ; range of 5, 6, 7, 8, 9, 10
|
||||||
|
|
||||||
byte[] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
|
byte[] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
|
||||||
|
|
||||||
@ -464,9 +470,9 @@ If you're not interested in the return value, prefix the function call with the
|
|||||||
Otherwise the compiler will warn you about discarding the result of the call.
|
Otherwise the compiler will warn you about discarding the result of the call.
|
||||||
|
|
||||||
Normal subroutines can only return zero or one return values.
|
Normal subroutines can only return zero or one return values.
|
||||||
However, the special ``asmsub`` routines (implemented in assembly code or referencing
|
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
|
||||||
a routine in kernel ROM) can return more than one return values, for instance a status
|
(referencing a routine in kernel ROM) can return more than one return value.
|
||||||
in the carry bit and a number in A, or a 16-bit value in A/Y registers.
|
For example a status in the carry bit and a number in A, or a 16-bit value in A/Y registers.
|
||||||
It is not possible to process the results of a call to these kind of routines
|
It is not possible to process the results of a call to these kind of routines
|
||||||
directly from the language, because only single value assignments are possible.
|
directly from the language, because only single value assignments are possible.
|
||||||
You can still call the subroutine and not store the results.
|
You can still call the subroutine and not store the results.
|
||||||
@ -494,9 +500,34 @@ and can have nothing following it. The close curly brace must be on its own line
|
|||||||
The parameters is a (possibly empty) comma separated list of "<datatype> <parametername>" pairs specifying the input parameters.
|
The parameters is a (possibly empty) comma separated list of "<datatype> <parametername>" pairs specifying the input parameters.
|
||||||
The return type has to be specified if the subroutine returns a value.
|
The return type has to be specified if the subroutine returns a value.
|
||||||
|
|
||||||
.. todo::
|
|
||||||
asmsub with assigning memory address to refer to predefined ROM subroutines
|
Assembly / ROM subroutines
|
||||||
asmsub with a regular body to precisely control what registers are used to call the subroutine
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Subroutines implemented in ROM are usually defined by compiler library files, with the following syntax::
|
||||||
|
|
||||||
|
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> clobbers() -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y
|
||||||
|
|
||||||
|
This defines the ``LOAD`` subroutine at ROM memory address $FFD5, taking arguments in all three registers A, X and Y,
|
||||||
|
and returning stuff in several registers as well. The ``clobbers`` clause is used to signify to the compiler
|
||||||
|
what CPU registers are clobbered by the call instead of being unchanged or returning a meaningful result value.
|
||||||
|
|
||||||
|
|
||||||
|
Subroutines that are implemented purely in assembly code and which have an assembly calling convention (i.e.
|
||||||
|
the parameters are strictly passed via cpu registers), are defined like this::
|
||||||
|
|
||||||
|
asmsub FREADS32() clobbers(A,X,Y) {
|
||||||
|
%asm {{
|
||||||
|
lda $62
|
||||||
|
eor #$ff
|
||||||
|
asl a
|
||||||
|
lda #0
|
||||||
|
ldx #$a0
|
||||||
|
jmp $bc4f
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
the statement body of such a subroutine should consist of just an inline assembly block.
|
||||||
|
|
||||||
|
|
||||||
Expressions
|
Expressions
|
||||||
@ -518,7 +549,7 @@ Loops
|
|||||||
for loop
|
for loop
|
||||||
^^^^^^^^
|
^^^^^^^^
|
||||||
|
|
||||||
The loop variable must be a register or a byte/word variable,
|
The loop variable must be a byte or word variable,
|
||||||
and must be defined first in the local scope of the for loop.
|
and must be defined first in the local scope of the for loop.
|
||||||
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
|
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
|
||||||
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
|
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
|
||||||
@ -565,19 +596,34 @@ You can use a single statement, or a statement block like in the example below::
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
repeat--until loop
|
do-until loop
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
Until the given condition is true (1), repeat the given statement(s).
|
Until the given condition is true (1), repeat the given statement(s).
|
||||||
You can use a single statement, or a statement block like in the example below::
|
You can use a single statement, or a statement block like in the example below::
|
||||||
|
|
||||||
repeat {
|
do {
|
||||||
; do something...
|
; do something...
|
||||||
break ; break out of the loop
|
break ; break out of the loop
|
||||||
continue ; immediately enter next iteration
|
continue ; immediately enter next iteration
|
||||||
} until <condition>
|
} until <condition>
|
||||||
|
|
||||||
|
|
||||||
|
repeat loop
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
When you're only interested in repeating something a given number of times.
|
||||||
|
It's a short hand for a for loop without an explicit loop variable::
|
||||||
|
|
||||||
|
repeat 15 {
|
||||||
|
; do something...
|
||||||
|
break ; you can break out of the loop
|
||||||
|
}
|
||||||
|
|
||||||
|
If you omit the iteration count, it simply loops forever.
|
||||||
|
You can still ``break`` out of such a loop if you want though.
|
||||||
|
|
||||||
|
|
||||||
Conditional Execution and Jumps
|
Conditional Execution and Jumps
|
||||||
-------------------------------
|
-------------------------------
|
||||||
|
|
||||||
@ -656,3 +702,4 @@ case you have to use { } to enclose them::
|
|||||||
}
|
}
|
||||||
else -> c64scr.print("don't know")
|
else -> c64scr.print("don't know")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,22 +113,14 @@ CPU
|
|||||||
Directly Usable Registers
|
Directly Usable Registers
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
The following 6502 CPU hardware registers are directly usable in program code (and are reserved symbols):
|
The hardware CPU registers are not directly accessible from regular Prog8 code.
|
||||||
|
If you need to mess with them, you'll have to use inline assembly.
|
||||||
|
Be extra wary of the ``X`` register because it is used as an evaluation stack pointer and
|
||||||
|
changing its value you will destroy the evaluation stack and likely crash the program.
|
||||||
|
|
||||||
- ``A``, ``X``, ``Y`` the three main cpu registers (8 bits)
|
The status register (P) carry flag and interrupt disable flag can be written via a couple of special
|
||||||
- the status register (P) carry flag and interrupt disable flag can be written via a couple of special
|
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
|
||||||
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
|
and read via the ``read_flags()`` function.
|
||||||
and read via the ``read_flags()`` function.
|
|
||||||
|
|
||||||
However, you must assume that the 3 hardware registers ``A``, ``X`` and ``Y``
|
|
||||||
are volatile. Their values cannot be depended upon, the compiler will use them as required.
|
|
||||||
Even simple assignments may require modification of one or more of the registers (for instance, when using arrays).
|
|
||||||
|
|
||||||
Even more important, the ``X`` register is used as an evaluation stack pointer.
|
|
||||||
If you mess with it, you will destroy the evaluation stack and likely crash your program.
|
|
||||||
In some cases the compiler will warn you about this, but you should really avoid to use
|
|
||||||
this register. It's possible to store/restore the register's value (using special built in functions)
|
|
||||||
for the cases you really really need to use it directly.
|
|
||||||
|
|
||||||
|
|
||||||
Subroutine Calling Conventions
|
Subroutine Calling Conventions
|
||||||
@ -173,3 +165,4 @@ as a subroutine ``irq`` in the module ``irq`` so like this::
|
|||||||
; ... irq handling here ...
|
; ... irq handling here ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,24 +2,11 @@
|
|||||||
TODO
|
TODO
|
||||||
====
|
====
|
||||||
|
|
||||||
- option to load library files from a directory instead of the embedded ones
|
- finalize (most) of the still missing "new" assignment asm code generation
|
||||||
- @"zzz" screencode encoded strings + add this to docs too
|
- aliases for imported symbols for example perhaps '%alias print = c64scr.print'
|
||||||
|
- option to load library files from a directory instead of the embedded ones (easier library development/debugging)
|
||||||
|
- investigate support for 8bitguy's Commander X16 platform https://www.commanderx16.com and https://github.com/commanderx16/x16-docs
|
||||||
Memory Block Operations integrated in language?
|
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
||||||
|
|
||||||
array/string memory block operations?
|
|
||||||
|
|
||||||
- array operations
|
|
||||||
copy (from another array with the same length), shift-N(left,right), rotate-N(left,right)
|
|
||||||
clear (set whole array to the given value, default 0)
|
|
||||||
|
|
||||||
- array operations ofcourse work identical on vars and on memory mapped vars of these types.
|
|
||||||
|
|
||||||
- strings: identical operations as on array.
|
|
||||||
|
|
||||||
For now, we have the ``memcopy`` and ``memset`` builtin functions.
|
|
||||||
|
|
||||||
|
|
||||||
More optimizations
|
More optimizations
|
||||||
@ -27,12 +14,16 @@ More optimizations
|
|||||||
|
|
||||||
Add more compiler optimizations to the existing ones.
|
Add more compiler optimizations to the existing ones.
|
||||||
|
|
||||||
- on the language AST level
|
- more targeted optimizations for assigment asm code, such as the following:
|
||||||
- on the final assembly source level
|
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
|
||||||
|
- remove unreachable code after an exit(), return or goto
|
||||||
|
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
|
||||||
|
the program will then rely solely on the values as they are in memory at the time of program startup.
|
||||||
|
- Also some library routines and code patterns could perhaps be optimized further
|
||||||
- can the parameter passing to subroutines be optimized to avoid copying?
|
- can the parameter passing to subroutines be optimized to avoid copying?
|
||||||
- working subroutine inlining (taking care of vars and identifier refs to them)
|
- more optimizations on the language AST level
|
||||||
|
- more optimizations on the final assembly source level
|
||||||
Also some library routines and code patterns could perhaps be optimized further
|
- note: abandoned subroutine inlining because of problems referencing non-local stuff. Can't move everything around.
|
||||||
|
|
||||||
|
|
||||||
Eval stack redesign? (lot of work)
|
Eval stack redesign? (lot of work)
|
||||||
@ -40,17 +31,18 @@ Eval stack redesign? (lot of work)
|
|||||||
|
|
||||||
The eval stack is now a split lsb/msb stack using X as the stackpointer.
|
The eval stack is now a split lsb/msb stack using X as the stackpointer.
|
||||||
Is it easier/faster to just use a single page unsplit stack?
|
Is it easier/faster to just use a single page unsplit stack?
|
||||||
It could then even be moved into the zeropage to greatly reduce code size and slowness.
|
It could then even be moved into the zeropage to reduce code size and slowness.
|
||||||
|
|
||||||
Or just move the LSB portion into a slab of the zeropage.
|
Or just move the LSB portion into a slab of the zeropage.
|
||||||
|
|
||||||
Allocate a fixed word in ZP that is the TOS so we can always operate on TOS directly
|
Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly
|
||||||
without having to to index into the stack?
|
without having to index with X into the eval stack all the time?
|
||||||
|
This could GREATLY improve code size and speed for operations that work on just a single value.
|
||||||
|
|
||||||
|
|
||||||
Bugs
|
Bug Fixing
|
||||||
^^^^
|
^^^^^^^^^^
|
||||||
Ofcourse there are still bugs to fix ;)
|
Ofcourse there are always bugs to fix ;)
|
||||||
|
|
||||||
|
|
||||||
Misc
|
Misc
|
||||||
@ -58,4 +50,3 @@ Misc
|
|||||||
|
|
||||||
Several ideas were discussed on my reddit post
|
Several ideas were discussed on my reddit post
|
||||||
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/
|
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/
|
||||||
|
|
||||||
|
@ -104,7 +104,6 @@ main {
|
|||||||
ub = all(farr)
|
ub = all(farr)
|
||||||
if ub==0 c64scr.print("error all10\n")
|
if ub==0 c64scr.print("error all10\n")
|
||||||
|
|
||||||
|
c64scr.print("\nyou should see no errors printed above (only at first run).")
|
||||||
c64scr.print("\nyou should see no errors above.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,6 @@ main {
|
|||||||
|
|
||||||
div_float(0,1,0)
|
div_float(0,1,0)
|
||||||
div_float(999.9,111.0,9.008108108108107)
|
div_float(999.9,111.0,9.008108108108107)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub div_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
sub div_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||||
|
@ -32,7 +32,6 @@ main {
|
|||||||
minus_float(0,0,0)
|
minus_float(0,0,0)
|
||||||
minus_float(2.5,1.5,1.0)
|
minus_float(2.5,1.5,1.0)
|
||||||
minus_float(-1.5,3.5,-5.0)
|
minus_float(-1.5,3.5,-5.0)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||||
|
@ -26,7 +26,6 @@ main {
|
|||||||
mul_float(0,0,0)
|
mul_float(0,0,0)
|
||||||
mul_float(2.5,10,25)
|
mul_float(2.5,10,25)
|
||||||
mul_float(-1.5,10,-15)
|
mul_float(-1.5,10,-15)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||||
|
@ -30,7 +30,6 @@ main {
|
|||||||
plus_float(1.5,2.5,4.0)
|
plus_float(1.5,2.5,4.0)
|
||||||
plus_float(-1.5,3.5,2.0)
|
plus_float(-1.5,3.5,2.0)
|
||||||
plus_float(-1.1,3.3,2.2)
|
plus_float(-1.1,3.3,2.2)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub plus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
sub plus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
main {
|
main {
|
||||||
|
|
||||||
|
|
||||||
sub start() {
|
sub start() {
|
||||||
|
|
||||||
c64scr.plot(0,24)
|
c64scr.plot(0,24)
|
||||||
|
|
||||||
|
ubyte Y
|
||||||
ubyte ub=200
|
ubyte ub=200
|
||||||
byte bb=-100
|
byte bb=-100
|
||||||
uword uw = 2000
|
uword uw = 2000
|
||||||
@ -64,6 +64,7 @@ main {
|
|||||||
warr[1]--
|
warr[1]--
|
||||||
flarr[1] --
|
flarr[1] --
|
||||||
check_ub(ub, 200)
|
check_ub(ub, 200)
|
||||||
|
|
||||||
Y=100
|
Y=100
|
||||||
Y--
|
Y--
|
||||||
check_ub(Y, 99)
|
check_ub(Y, 99)
|
||||||
@ -76,8 +77,6 @@ main {
|
|||||||
check_b(barr[1], -100)
|
check_b(barr[1], -100)
|
||||||
check_uw(uwarr[1], 2000)
|
check_uw(uwarr[1], 2000)
|
||||||
check_w(warr[1], -1000)
|
check_w(warr[1], -1000)
|
||||||
|
|
||||||
@($0400+400-1) = X
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sub check_ub(ubyte value, ubyte expected) {
|
sub check_ub(ubyte value, ubyte expected) {
|
||||||
|
130
examples/arithmetic/sgn.p8
Normal file
130
examples/arithmetic/sgn.p8
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
%import c64flt
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
byte b1
|
||||||
|
byte b2
|
||||||
|
ubyte ub1
|
||||||
|
ubyte ub2
|
||||||
|
word w1
|
||||||
|
word w2
|
||||||
|
uword uw1
|
||||||
|
uword uw2
|
||||||
|
float f1
|
||||||
|
float f2
|
||||||
|
|
||||||
|
b1 = 10
|
||||||
|
b2 = 10
|
||||||
|
if sgn(b2-b1) != 0
|
||||||
|
c64scr.print("sgn1 error1\n")
|
||||||
|
|
||||||
|
b1 = -100
|
||||||
|
b2 = -100
|
||||||
|
if sgn(b2-b1) != 0
|
||||||
|
c64scr.print("sgn1 error2\n")
|
||||||
|
|
||||||
|
ub1 = 200
|
||||||
|
ub2 = 200
|
||||||
|
if sgn(ub2-ub1) != 0
|
||||||
|
c64scr.print("sgn1 error3\n")
|
||||||
|
|
||||||
|
w1 = 100
|
||||||
|
w2 = 100
|
||||||
|
if sgn(w2-w1) != 0
|
||||||
|
c64scr.print("sgn1 error4\n")
|
||||||
|
|
||||||
|
w1 = -2000
|
||||||
|
w2 = -2000
|
||||||
|
if sgn(w2-w1) != 0
|
||||||
|
c64scr.print("sgn1 error5\n")
|
||||||
|
|
||||||
|
uw1 = 999
|
||||||
|
uw2 = 999
|
||||||
|
if sgn(uw2-uw1) != 0
|
||||||
|
c64scr.print("sgn1 error6\n")
|
||||||
|
|
||||||
|
f1 = 3.45
|
||||||
|
f2 = 3.45
|
||||||
|
if sgn(f2-f1) != 0
|
||||||
|
c64scr.print("sgn1 error7\n")
|
||||||
|
|
||||||
|
|
||||||
|
; -1
|
||||||
|
b1 = 11
|
||||||
|
b2 = 10
|
||||||
|
if sgn(b2-b1) != -1
|
||||||
|
c64scr.print("sgn2 error1\n")
|
||||||
|
|
||||||
|
b1 = -10
|
||||||
|
b2 = -100
|
||||||
|
if sgn(b2-b1) != -1
|
||||||
|
c64scr.print("sgn2 error2\n")
|
||||||
|
|
||||||
|
ub1 = 202
|
||||||
|
ub2 = 200
|
||||||
|
if sgn(ub2 as byte - ub1 as byte) != -1
|
||||||
|
c64scr.print("sgn2 error3\n")
|
||||||
|
|
||||||
|
w1 = 101
|
||||||
|
w2 = 100
|
||||||
|
if sgn(w2-w1) != -1
|
||||||
|
c64scr.print("sgn2 error4\n")
|
||||||
|
|
||||||
|
w1 = -200
|
||||||
|
w2 = -2000
|
||||||
|
if sgn(w2-w1) != -1
|
||||||
|
c64scr.print("sgn2 error5\n")
|
||||||
|
|
||||||
|
uw1 = 2222
|
||||||
|
uw2 = 999
|
||||||
|
if sgn((uw2 as word) - (uw1 as word)) != -1
|
||||||
|
c64scr.print("sgn2 error6a\n")
|
||||||
|
if sgn(uw2 - uw1) != 1 ; always 0 or 1 if unsigned
|
||||||
|
c64scr.print("sgn2 error6b\n")
|
||||||
|
|
||||||
|
f1 = 3.45
|
||||||
|
f2 = 1.11
|
||||||
|
if sgn(f2-f1) != -1
|
||||||
|
c64scr.print("sgn2 error7\n")
|
||||||
|
|
||||||
|
; +1
|
||||||
|
b1 = 11
|
||||||
|
b2 = 20
|
||||||
|
if sgn(b2-b1) != 1
|
||||||
|
c64scr.print("sgn3 error1\n")
|
||||||
|
|
||||||
|
b1 = -10
|
||||||
|
b2 = -1
|
||||||
|
if sgn(b2-b1) != 1
|
||||||
|
c64scr.print("sgn3 error2\n")
|
||||||
|
|
||||||
|
ub1 = 202
|
||||||
|
ub2 = 205
|
||||||
|
if sgn(ub2-ub1) != 1
|
||||||
|
c64scr.print("sgn3 error3\n")
|
||||||
|
|
||||||
|
w1 = 101
|
||||||
|
w2 = 200
|
||||||
|
if sgn(w2-w1) != 1
|
||||||
|
c64scr.print("sgn3 error4\n")
|
||||||
|
|
||||||
|
w1 = -200
|
||||||
|
w2 = -20
|
||||||
|
if sgn(w2-w1) != 1
|
||||||
|
c64scr.print("sgn3 error5\n")
|
||||||
|
|
||||||
|
uw1 = 2222
|
||||||
|
uw2 = 9999
|
||||||
|
if sgn(uw2-uw1) != 1
|
||||||
|
c64scr.print("sgn3 error6\n")
|
||||||
|
|
||||||
|
f1 = 3.45
|
||||||
|
f2 = 5.11
|
||||||
|
if sgn(f2-f1) != 1
|
||||||
|
c64scr.print("sgn3 error7\n")
|
||||||
|
|
||||||
|
c64scr.print("should see no sgn errors\n")
|
||||||
|
}
|
||||||
|
}
|
126
examples/balloonflight.p8
Normal file
126
examples/balloonflight.p8
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
%import c64lib
|
||||||
|
%import c64utils
|
||||||
|
%zeropage basicsafe
|
||||||
|
|
||||||
|
main {
|
||||||
|
|
||||||
|
ubyte perform_scroll = false
|
||||||
|
|
||||||
|
sub start() {
|
||||||
|
c64.SPRPTR[0] = $0f00 / 64
|
||||||
|
c64.SPENA = 1
|
||||||
|
c64.SP0COL = 14
|
||||||
|
c64.SPXY[0] = 80
|
||||||
|
c64.SPXY[1] = 100
|
||||||
|
|
||||||
|
c64.SCROLX &= %11110111 ; 38 column mode
|
||||||
|
|
||||||
|
c64utils.set_rasterirq(1) ; enable animation
|
||||||
|
|
||||||
|
ubyte target_height = 10
|
||||||
|
ubyte active_height = 24
|
||||||
|
ubyte upwards = true
|
||||||
|
|
||||||
|
repeat {
|
||||||
|
ubyte mountain = 223 ; slope upwards
|
||||||
|
if active_height < target_height {
|
||||||
|
active_height++
|
||||||
|
upwards = true
|
||||||
|
} else if active_height > target_height {
|
||||||
|
mountain = 233 ; slope downwards
|
||||||
|
active_height--
|
||||||
|
upwards = false
|
||||||
|
} else {
|
||||||
|
target_height = 8 + rnd() % 16
|
||||||
|
if upwards
|
||||||
|
mountain = 233
|
||||||
|
else
|
||||||
|
mountain = 223
|
||||||
|
}
|
||||||
|
|
||||||
|
while not perform_scroll {
|
||||||
|
; let the raster irq do its timing job
|
||||||
|
}
|
||||||
|
|
||||||
|
perform_scroll = false
|
||||||
|
c64scr.scroll_left_full(true)
|
||||||
|
if c64.RASTER & 1
|
||||||
|
c64.SPXY[1] ++
|
||||||
|
else
|
||||||
|
c64.SPXY[1] --
|
||||||
|
|
||||||
|
ubyte yy
|
||||||
|
for yy in 0 to active_height-1 {
|
||||||
|
c64scr.setcc(39, yy, 32, 2) ; clear top of screen
|
||||||
|
}
|
||||||
|
c64scr.setcc(39, active_height, mountain, 8) ; mountain edge
|
||||||
|
for yy in active_height+1 to 24 {
|
||||||
|
c64scr.setcc(39, yy, 160, 8) ; draw mountain
|
||||||
|
}
|
||||||
|
|
||||||
|
yy = rnd()
|
||||||
|
if yy > 100 {
|
||||||
|
; draw a star
|
||||||
|
c64scr.setcc(39, yy % (active_height-1), '.', rnd())
|
||||||
|
}
|
||||||
|
|
||||||
|
if yy > 200 {
|
||||||
|
; draw a tree
|
||||||
|
ubyte tree = 30
|
||||||
|
ubyte treecolor = 5
|
||||||
|
if yy & %01000000 != 0
|
||||||
|
tree = 88
|
||||||
|
else if yy & %00100000 != 0
|
||||||
|
tree = 65
|
||||||
|
if rnd() > 130
|
||||||
|
treecolor = 13
|
||||||
|
c64scr.setcc(39, active_height, tree, treecolor)
|
||||||
|
}
|
||||||
|
|
||||||
|
if yy > 235 {
|
||||||
|
; draw a camel
|
||||||
|
c64scr.setcc(39, active_height, 94, 9)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
spritedata $0f00 {
|
||||||
|
; this memory block contains the sprite data
|
||||||
|
; it must start on an address aligned to 64 bytes.
|
||||||
|
%option force_output ; make sure the data in this block appears in the resulting program
|
||||||
|
|
||||||
|
ubyte[] balloonsprite = [ %00000000,%01111111,%00000000,
|
||||||
|
%00000001,%11111111,%11000000,
|
||||||
|
%00000011,%11111111,%11100000,
|
||||||
|
%00000011,%11100011,%11100000,
|
||||||
|
%00000111,%11011100,%11110000,
|
||||||
|
%00000111,%11011101,%11110000,
|
||||||
|
%00000111,%11011100,%11110000,
|
||||||
|
%00000011,%11100011,%11100000,
|
||||||
|
%00000011,%11111111,%11100000,
|
||||||
|
%00000011,%11111111,%11100000,
|
||||||
|
%00000010,%11111111,%10100000,
|
||||||
|
%00000001,%01111111,%01000000,
|
||||||
|
%00000001,%00111110,%01000000,
|
||||||
|
%00000000,%10011100,%10000000,
|
||||||
|
%00000000,%10011100,%10000000,
|
||||||
|
%00000000,%01001001,%00000000,
|
||||||
|
%00000000,%01001001,%00000000,
|
||||||
|
%00000000,%00111110,%00000000,
|
||||||
|
%00000000,%00111110,%00000000,
|
||||||
|
%00000000,%00111110,%00000000,
|
||||||
|
%00000000,%00011100,%00000000 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
irq {
|
||||||
|
ubyte smoothx=7
|
||||||
|
sub irq() {
|
||||||
|
smoothx = (smoothx-1) & 7
|
||||||
|
main.perform_scroll = smoothx==0
|
||||||
|
c64.SCROLX = (c64.SCROLX & %11111000) | smoothx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,7 @@ sub start() {
|
|||||||
void c64.CHRIN()
|
void c64.CHRIN()
|
||||||
c64.CLEARSCR()
|
c64.CLEARSCR()
|
||||||
|
|
||||||
while(true) {
|
repeat {
|
||||||
uword note
|
uword note
|
||||||
for note in notes {
|
for note in notes {
|
||||||
ubyte note1 = lsb(note)
|
ubyte note1 = lsb(note)
|
||||||
@ -34,17 +34,16 @@ sub start() {
|
|||||||
delay()
|
delay()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub delay() {
|
sub delay() {
|
||||||
ubyte d
|
repeat 32 {
|
||||||
for d in 0 to 12 {
|
while c64.RASTER {
|
||||||
while(c64.RASTER!=0) {
|
|
||||||
; tempo delay synced to screen refresh
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub print_notes(ubyte n1, ubyte n2) {
|
sub print_notes(ubyte n1, ubyte n2) {
|
||||||
c64.CHROUT('\n')
|
c64.CHROUT('\n')
|
||||||
c64scr.plot(n1/2, 24)
|
c64scr.plot(n1/2, 24)
|
||||||
c64.COLOR=7
|
c64.COLOR=7
|
||||||
@ -52,9 +51,9 @@ sub start() {
|
|||||||
c64scr.plot(n2/2, 24)
|
c64scr.plot(n2/2, 24)
|
||||||
c64.COLOR=4
|
c64.COLOR=4
|
||||||
c64.CHROUT('Q')
|
c64.CHROUT('Q')
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
; details about the boulderdash music can be found here:
|
; details about the boulderdash music can be found here:
|
||||||
; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune
|
; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune
|
||||||
|
|
||||||
|
252
examples/c64graphics.p8
Normal file
252
examples/c64graphics.p8
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
%import c64lib
|
||||||
|
|
||||||
|
; bitmap pixel graphics module for the C64
|
||||||
|
; only black/white monchrome for now
|
||||||
|
|
||||||
|
; you could put this code at $4000 which is after the bitmap screen in memory ($2000-$3fff),
|
||||||
|
; this leaves more space for user program code.
|
||||||
|
|
||||||
|
graphics {
|
||||||
|
const uword bitmap_address = $2000
|
||||||
|
|
||||||
|
sub enable_bitmap_mode() {
|
||||||
|
; enable bitmap screen, erase it and set colors to black/white.
|
||||||
|
c64.SCROLY |= %00100000
|
||||||
|
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
|
||||||
|
memset(bitmap_address, 320*200/8, 0)
|
||||||
|
c64scr.clear_screen($10, 0) ; pixel color $1 (white) backround $0 (black)
|
||||||
|
}
|
||||||
|
|
||||||
|
sub line(uword x1, ubyte y1, uword x2, ubyte y2) {
|
||||||
|
; Bresenham algorithm.
|
||||||
|
; This code special cases various quadrant loops to allow simple ++ and -- operations.
|
||||||
|
if y1>y2 {
|
||||||
|
; make sure dy is always positive to avoid 8 instead of just 4 special cases
|
||||||
|
swap(x1, x2)
|
||||||
|
swap(y1, y2)
|
||||||
|
}
|
||||||
|
word d = 0
|
||||||
|
ubyte positive_ix = true
|
||||||
|
word dx = x2 - x1 as word
|
||||||
|
word dy = y2 as word - y1 as word
|
||||||
|
if dx < 0 {
|
||||||
|
dx = -dx
|
||||||
|
positive_ix = false
|
||||||
|
}
|
||||||
|
dx *= 2
|
||||||
|
dy *= 2
|
||||||
|
plotx = x1
|
||||||
|
|
||||||
|
if dx >= dy {
|
||||||
|
if positive_ix {
|
||||||
|
repeat {
|
||||||
|
plot(y1)
|
||||||
|
if plotx==x2
|
||||||
|
return
|
||||||
|
plotx++
|
||||||
|
d += dy
|
||||||
|
if d > dx {
|
||||||
|
y1++
|
||||||
|
d -= dx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat {
|
||||||
|
plot(y1)
|
||||||
|
if plotx==x2
|
||||||
|
return
|
||||||
|
plotx--
|
||||||
|
d += dy
|
||||||
|
if d > dx {
|
||||||
|
y1++
|
||||||
|
d -= dx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if positive_ix {
|
||||||
|
repeat {
|
||||||
|
plot(y1)
|
||||||
|
if y1 == y2
|
||||||
|
return
|
||||||
|
y1++
|
||||||
|
d += dx
|
||||||
|
if d > dy {
|
||||||
|
plotx++
|
||||||
|
d -= dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
repeat {
|
||||||
|
plot(y1)
|
||||||
|
if y1 == y2
|
||||||
|
return
|
||||||
|
y1++
|
||||||
|
d += dx
|
||||||
|
if d > dy {
|
||||||
|
plotx--
|
||||||
|
d -= dy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||||
|
; Midpoint algorithm
|
||||||
|
ubyte ploty
|
||||||
|
ubyte xx = radius
|
||||||
|
ubyte yy = 0
|
||||||
|
byte decisionOver2 = 1-xx as byte
|
||||||
|
|
||||||
|
while xx>=yy {
|
||||||
|
plotx = xcenter + xx
|
||||||
|
ploty = ycenter + yy
|
||||||
|
plot(ploty)
|
||||||
|
plotx = xcenter - xx
|
||||||
|
plot(ploty)
|
||||||
|
plotx = xcenter + xx
|
||||||
|
ploty = ycenter - yy
|
||||||
|
plot(ploty)
|
||||||
|
plotx = xcenter - xx
|
||||||
|
plot(ploty)
|
||||||
|
plotx = xcenter + yy
|
||||||
|
ploty = ycenter + xx
|
||||||
|
plot(ploty)
|
||||||
|
plotx = xcenter - yy
|
||||||
|
plot(ploty)
|
||||||
|
plotx = xcenter + yy
|
||||||
|
ploty = ycenter - xx
|
||||||
|
plot(ploty)
|
||||||
|
plotx = xcenter - yy
|
||||||
|
plot(ploty)
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0
|
||||||
|
decisionOver2 += 2*yy+1
|
||||||
|
else {
|
||||||
|
xx--
|
||||||
|
decisionOver2 += 2*(yy-xx)+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub disc(uword cx, ubyte cy, ubyte radius) {
|
||||||
|
; Midpoint algorithm, filled
|
||||||
|
ubyte xx = radius
|
||||||
|
ubyte yy = 0
|
||||||
|
byte decisionOver2 = 1-xx as byte
|
||||||
|
|
||||||
|
while xx>=yy {
|
||||||
|
ubyte cy_plus_yy = cy + yy
|
||||||
|
ubyte cy_min_yy = cy - yy
|
||||||
|
ubyte cy_plus_xx = cy + xx
|
||||||
|
ubyte cy_min_xx = cy - xx
|
||||||
|
|
||||||
|
for plotx in cx to cx+xx {
|
||||||
|
plot(cy_plus_yy)
|
||||||
|
plot(cy_min_yy)
|
||||||
|
}
|
||||||
|
for plotx in cx-xx to cx-1 {
|
||||||
|
plot(cy_plus_yy)
|
||||||
|
plot(cy_min_yy)
|
||||||
|
}
|
||||||
|
for plotx in cx to cx+yy {
|
||||||
|
plot(cy_plus_xx)
|
||||||
|
plot(cy_min_xx)
|
||||||
|
}
|
||||||
|
for plotx in cx-yy to cx {
|
||||||
|
plot(cy_plus_xx)
|
||||||
|
plot(cy_min_xx)
|
||||||
|
}
|
||||||
|
yy++
|
||||||
|
if decisionOver2<=0
|
||||||
|
decisionOver2 += 2*yy+1
|
||||||
|
else {
|
||||||
|
xx--
|
||||||
|
decisionOver2 += 2*(yy-xx)+1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
; here is the non-asm code for the plot routine below:
|
||||||
|
; sub plot_nonasm(uword px, ubyte py) {
|
||||||
|
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||||
|
; uword addr = bitmap_address + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
|
||||||
|
; @(addr) |= ormask[lsb(px) & 7]
|
||||||
|
; }
|
||||||
|
|
||||||
|
uword plotx ; 0..319 ; separate 'parameter' for plot()
|
||||||
|
|
||||||
|
asmsub plot(ubyte ploty @A) { ; plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||||
|
%asm {{
|
||||||
|
tay
|
||||||
|
stx c64.SCRATCH_ZPREGX
|
||||||
|
lda plotx+1
|
||||||
|
sta c64.SCRATCH_ZPWORD2+1
|
||||||
|
lsr a ; 0
|
||||||
|
sta c64.SCRATCH_ZPWORD2
|
||||||
|
lda plotx
|
||||||
|
pha
|
||||||
|
and #7
|
||||||
|
tax
|
||||||
|
|
||||||
|
lda _y_lookup_lo,y
|
||||||
|
clc
|
||||||
|
adc c64.SCRATCH_ZPWORD2
|
||||||
|
sta c64.SCRATCH_ZPWORD2
|
||||||
|
lda _y_lookup_hi,y
|
||||||
|
adc c64.SCRATCH_ZPWORD2+1
|
||||||
|
sta c64.SCRATCH_ZPWORD2+1
|
||||||
|
|
||||||
|
pla ; plotx
|
||||||
|
and #%11111000
|
||||||
|
tay
|
||||||
|
lda (c64.SCRATCH_ZPWORD2),y
|
||||||
|
ora _ormask,x
|
||||||
|
sta (c64.SCRATCH_ZPWORD2),y
|
||||||
|
|
||||||
|
ldx c64.SCRATCH_ZPREGX
|
||||||
|
rts
|
||||||
|
|
||||||
|
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
|
||||||
|
|
||||||
|
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
|
||||||
|
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
|
||||||
|
; the y lookup tables encodes this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||||
|
_y_lookup_hi
|
||||||
|
.byte $20, $20, $20, $20, $20, $20, $20, $20, $21, $21, $21, $21, $21, $21, $21, $21
|
||||||
|
.byte $22, $22, $22, $22, $22, $22, $22, $22, $23, $23, $23, $23, $23, $23, $23, $23
|
||||||
|
.byte $25, $25, $25, $25, $25, $25, $25, $25, $26, $26, $26, $26, $26, $26, $26, $26
|
||||||
|
.byte $27, $27, $27, $27, $27, $27, $27, $27, $28, $28, $28, $28, $28, $28, $28, $28
|
||||||
|
.byte $2a, $2a, $2a, $2a, $2a, $2a, $2a, $2a, $2b, $2b, $2b, $2b, $2b, $2b, $2b, $2b
|
||||||
|
.byte $2c, $2c, $2c, $2c, $2c, $2c, $2c, $2c, $2d, $2d, $2d, $2d, $2d, $2d, $2d, $2d
|
||||||
|
.byte $2f, $2f, $2f, $2f, $2f, $2f, $2f, $2f, $30, $30, $30, $30, $30, $30, $30, $30
|
||||||
|
.byte $31, $31, $31, $31, $31, $31, $31, $31, $32, $32, $32, $32, $32, $32, $32, $32
|
||||||
|
.byte $34, $34, $34, $34, $34, $34, $34, $34, $35, $35, $35, $35, $35, $35, $35, $35
|
||||||
|
.byte $36, $36, $36, $36, $36, $36, $36, $36, $37, $37, $37, $37, $37, $37, $37, $37
|
||||||
|
.byte $39, $39, $39, $39, $39, $39, $39, $39, $3a, $3a, $3a, $3a, $3a, $3a, $3a, $3a
|
||||||
|
.byte $3b, $3b, $3b, $3b, $3b, $3b, $3b, $3b, $3c, $3c, $3c, $3c, $3c, $3c, $3c, $3c
|
||||||
|
.byte $3e, $3e, $3e, $3e, $3e, $3e, $3e, $3e
|
||||||
|
|
||||||
|
_y_lookup_lo
|
||||||
|
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||||
|
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||||
|
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||||
|
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||||
|
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||||
|
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||||
|
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||||
|
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||||
|
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||||
|
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||||
|
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||||
|
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||||
|
.byte $00, $01, $02, $03, $04, $05, $06, $07
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -105,13 +105,5 @@ main {
|
|||||||
c64scr.print("ok: 22 >= 22\n")
|
c64scr.print("ok: 22 >= 22\n")
|
||||||
else
|
else
|
||||||
c64scr.print("error in 22>=22!\n")
|
c64scr.print("error in 22>=22!\n")
|
||||||
|
|
||||||
ubyte endX = X
|
|
||||||
if endX == 255
|
|
||||||
c64scr.print("stack x ok!\n")
|
|
||||||
else
|
|
||||||
c64scr.print("error: stack x != 255 !\n")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -105,12 +105,5 @@ main {
|
|||||||
c64scr.print("ok: -22.2 >= -22.2\n")
|
c64scr.print("ok: -22.2 >= -22.2\n")
|
||||||
else
|
else
|
||||||
c64scr.print("error in -22.2>=-22.2!\n")
|
c64scr.print("error in -22.2>=-22.2!\n")
|
||||||
|
|
||||||
ubyte endX = X
|
|
||||||
if endX == 255
|
|
||||||
c64scr.print("stack x ok!\n")
|
|
||||||
else
|
|
||||||
c64scr.print("error: stack x != 255 !\n")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -105,13 +105,5 @@ main {
|
|||||||
c64scr.print("ok: 22 >= 22\n")
|
c64scr.print("ok: 22 >= 22\n")
|
||||||
else
|
else
|
||||||
c64scr.print("error in 22>=22!\n")
|
c64scr.print("error in 22>=22!\n")
|
||||||
|
|
||||||
ubyte endX = X
|
|
||||||
if endX == 255
|
|
||||||
c64scr.print("stack x ok!\n")
|
|
||||||
else
|
|
||||||
c64scr.print("error: stack x != 255 !\n")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -105,14 +105,5 @@ main {
|
|||||||
c64scr.print("ok: 322 >= 322\n")
|
c64scr.print("ok: 322 >= 322\n")
|
||||||
else
|
else
|
||||||
c64scr.print("error in 322>=322!\n")
|
c64scr.print("error in 322>=322!\n")
|
||||||
|
|
||||||
|
|
||||||
ubyte endX = X
|
|
||||||
if endX == 255
|
|
||||||
c64scr.print("stack x ok!\n")
|
|
||||||
else
|
|
||||||
c64scr.print("error: stack x != 255 !\n")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -137,14 +137,5 @@ main {
|
|||||||
c64scr.print("ok: 1000 >= 1000\n")
|
c64scr.print("ok: 1000 >= 1000\n")
|
||||||
else
|
else
|
||||||
c64scr.print("error in 1000>=1000!\n")
|
c64scr.print("error in 1000>=1000!\n")
|
||||||
|
|
||||||
|
|
||||||
ubyte endX = X
|
|
||||||
if endX == 255
|
|
||||||
c64scr.print("stack x ok!\n")
|
|
||||||
else
|
|
||||||
c64scr.print("error: stack x != 255 !\n")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,15 +52,8 @@ main {
|
|||||||
c64scr.print("v1=20, v2=-111\n")
|
c64scr.print("v1=20, v2=-111\n")
|
||||||
compare()
|
compare()
|
||||||
|
|
||||||
ubyte endX = X
|
|
||||||
if endX == 255
|
|
||||||
c64scr.print("\nstack x ok!\n")
|
|
||||||
else
|
|
||||||
c64scr.print("\nerror: stack x != 255 !\n")
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
sub compare() {
|
sub compare() {
|
||||||
c64scr.print(" == != < > <= >=\n")
|
c64scr.print(" == != < > <= >=\n")
|
||||||
|
|
||||||
@ -97,5 +90,4 @@ main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user