mirror of
https://github.com/irmen/prog8.git
synced 2025-06-15 02:23:36 +00:00
Compare commits
98 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -14,7 +14,7 @@ docs/build
|
||||
out/
|
||||
parser/**/*.interp
|
||||
parser/**/*.tokens
|
||||
|
||||
parser/**/*.java
|
||||
*.py[cod]
|
||||
*.egg
|
||||
*.egg-info
|
||||
|
13
README.md
13
README.md
@ -1,5 +1,6 @@
|
||||
[](https://saythanks.io/to/irmen)
|
||||
[](https://travis-ci.org/irmen/prog8)
|
||||
[](https://prog8.readthedocs.io/)
|
||||
|
||||
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
|
||||
===========================================================================
|
||||
@ -30,20 +31,18 @@ which aims to provide many conveniences over raw assembly code (even when using
|
||||
|
||||
Rapid edit-compile-run-debug cycle:
|
||||
|
||||
- use modern PC to work on
|
||||
- quick compilation times (seconds)
|
||||
- option to automatically run the program in the Vice emulator
|
||||
- use a modern PC to do the work on
|
||||
- very quick compilation times
|
||||
- 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
|
||||
- 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.
|
||||
|
||||
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
|
||||
--------------
|
||||
|
@ -1,11 +1,11 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.70"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.3.70"
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.3.72"
|
||||
id 'application'
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||
|
@ -658,8 +658,8 @@ func_all_f .proc
|
||||
dey
|
||||
cmp #0
|
||||
beq +
|
||||
cpy #255
|
||||
bne -
|
||||
cpy #255
|
||||
bne -
|
||||
lda #1
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
@ -739,3 +739,45 @@ sign_f .proc
|
||||
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
|
||||
|
@ -10,8 +10,8 @@
|
||||
c64flt {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
|
||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||
@ -34,7 +34,8 @@ c64flt {
|
||||
&float FL_PIHALF = $e2e0 ; PI / 2
|
||||
&float FL_TWOPI = $e2e5 ; 2 * PI
|
||||
&float FL_FR4 = $e2ea ; .25
|
||||
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
|
||||
; 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
|
||||
|
||||
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
@ -209,8 +210,8 @@ sub print_fln (float value) {
|
||||
; ---- prints the floating point value (with a newline at the end) using basic rom routines
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<print_fln_value
|
||||
ldy #>print_fln_value
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FPRINTLN ; print fac1 with newline
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
|
@ -565,14 +565,9 @@ asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
%asm {{
|
||||
ldy #0
|
||||
_loop sta c64.Screen,y
|
||||
sta c64.Screen+1,y
|
||||
sta c64.Screen+$0100,y
|
||||
sta c64.Screen+$0101,y
|
||||
sta c64.Screen+$0200,y
|
||||
sta c64.Screen+$0201,y
|
||||
sta c64.Screen+$02e8,y
|
||||
sta c64.Screen+$02e9,y
|
||||
iny
|
||||
iny
|
||||
bne _loop
|
||||
rts
|
||||
@ -585,14 +580,9 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||
%asm {{
|
||||
ldy #0
|
||||
_loop sta c64.Colors,y
|
||||
sta c64.Colors+1,y
|
||||
sta c64.Colors+$0100,y
|
||||
sta c64.Colors+$0101,y
|
||||
sta c64.Colors+$0200,y
|
||||
sta c64.Colors+$0201,y
|
||||
sta c64.Colors+$02e8,y
|
||||
sta c64.Colors+$02e9,y
|
||||
iny
|
||||
iny
|
||||
bne _loop
|
||||
rts
|
||||
@ -603,6 +593,7 @@ asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
bcs +
|
||||
@ -612,18 +603,7 @@ asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=12, 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
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Colors + 40*row + 1,x
|
||||
sta c64.Colors + 40*row,x
|
||||
.next
|
||||
@ -635,18 +615,7 @@ _scroll_screen ; scroll the screen memory
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=12, 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
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row,x
|
||||
.next
|
||||
@ -671,41 +640,23 @@ asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
+ ; scroll the color memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=12, row+=1
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Colors + 40*row + 0,x
|
||||
sta c64.Colors + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
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
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=12, row+=1
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
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
|
||||
rts
|
||||
}}
|
||||
@ -723,16 +674,7 @@ asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
+ ; scroll the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=11, 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
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row-1),x
|
||||
.next
|
||||
@ -742,16 +684,7 @@ asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
_scroll_screen ; scroll the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=11, 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
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
.next
|
||||
@ -775,16 +708,7 @@ asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
+ ; scroll the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=12, 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
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row+1),x
|
||||
.next
|
||||
@ -794,16 +718,7 @@ asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
_scroll_screen ; scroll the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=12, 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
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
.next
|
||||
@ -862,11 +777,14 @@ _print_byte_digits
|
||||
beq +
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
jmp _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq +
|
||||
beq _ones
|
||||
jsr c64.CHROUT
|
||||
+ txa
|
||||
_ones txa
|
||||
jsr c64.CHROUT
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
|
@ -239,7 +239,7 @@ mul_byte_3 .proc
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
mul_word_3 .proc
|
||||
; W*2 + W
|
||||
lda c64.ESTACK_HI+1,x
|
||||
@ -255,7 +255,7 @@ mul_word_3 .proc
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
mul_byte_5 .proc
|
||||
; X*4 + X
|
||||
@ -286,7 +286,7 @@ mul_word_5 .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
mul_byte_6 .proc
|
||||
; (X*2 + X)*2
|
||||
lda c64.ESTACK_LO+1,x
|
||||
@ -327,7 +327,7 @@ mul_byte_7 .proc
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
mul_word_7 .proc
|
||||
; W*8 - W
|
||||
lda c64.ESTACK_HI+1,x
|
||||
@ -411,7 +411,7 @@ mul_word_10 .proc
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
mul_byte_11 .proc
|
||||
; (X*2 + X)*4 - X
|
||||
lda c64.ESTACK_LO+1,x
|
||||
@ -488,7 +488,7 @@ mul_byte_14 .proc
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
; mul_word_14 is skipped (too much code)
|
||||
|
||||
mul_byte_15 .proc
|
||||
@ -604,7 +604,7 @@ mul_word_25 .proc
|
||||
adc c64.ESTACK_HI+1,x
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
.pend
|
||||
|
||||
mul_byte_40 .proc
|
||||
; (X*4 + X)*8
|
||||
@ -619,7 +619,7 @@ mul_byte_40 .proc
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
mul_word_40 .proc
|
||||
; (W*4 + W)*8
|
||||
lda c64.ESTACK_HI+1,x
|
||||
@ -680,3 +680,192 @@ _sign_possibly_zero lda c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.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
|
||||
|
||||
|
@ -1788,7 +1788,6 @@ ror2_mem_ub .proc
|
||||
|
||||
rol2_mem_ub .proc
|
||||
; -- in-place 8-bit rol of byte at memory location on stack
|
||||
;" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}"
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
@ -1803,57 +1802,279 @@ rol2_mem_ub .proc
|
||||
.pend
|
||||
|
||||
lsl_array_b .proc
|
||||
.warn "lsl_array_b" ; TODO
|
||||
.pend
|
||||
|
||||
lsl_array_w .proc
|
||||
.warn "lsl_array_w" ; TODO
|
||||
; -- lsl a (u)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
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
.warn "ror_array_ub" ; TODO
|
||||
.pend
|
||||
|
||||
ror_array_uw .proc
|
||||
.warn "ror_array_uw" ; TODO
|
||||
; -- 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
|
||||
ror a
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
@ -1 +1 @@
|
||||
1.81
|
||||
2.1
|
||||
|
@ -9,7 +9,6 @@ import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.vm.astvm.AstVm
|
||||
import java.io.IOException
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Path
|
||||
@ -40,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 dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
||||
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 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)
|
||||
@ -119,18 +117,6 @@ private fun compileMain(args: Array<String>) {
|
||||
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 (compilationResult.programName.isEmpty())
|
||||
println("\nCan't start emulator because no program was assembled.")
|
||||
|
@ -177,8 +177,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
private fun outputStatements(statements: List<Statement>) {
|
||||
for(stmt in statements) {
|
||||
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
|
||||
continue // skip autogenerated decls (to avoid generating a newline)
|
||||
outputi("")
|
||||
stmt.accept(this)
|
||||
output("\n")
|
||||
@ -283,16 +281,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
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)
|
||||
if (assignment.aug_op != null)
|
||||
if (assignment.aug_op != null && assignment.aug_op != "setvalue")
|
||||
output(" ${assignment.aug_op} ")
|
||||
else
|
||||
output(" = ")
|
||||
|
@ -3,6 +3,9 @@ package prog8.ast
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import java.nio.file.Path
|
||||
@ -33,6 +36,8 @@ interface Node {
|
||||
return this
|
||||
throw FatalAstException("scope missing from $this")
|
||||
}
|
||||
|
||||
fun replaceChildNode(node: Node, replacement: Node)
|
||||
}
|
||||
|
||||
interface IFunctionCall {
|
||||
@ -48,32 +53,31 @@ interface INameScope {
|
||||
|
||||
fun linkParents(parent: Node)
|
||||
|
||||
fun subScopes(): Map<String, INameScope> {
|
||||
val subscopes = mutableMapOf<String, INameScope>()
|
||||
fun subScope(name: String): INameScope? {
|
||||
for(stmt in statements) {
|
||||
when(stmt) {
|
||||
// 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 RepeatLoop -> subscopes[stmt.body.name] = stmt.body
|
||||
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
|
||||
is ForLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is RepeatLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is WhileLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is BranchStatement -> {
|
||||
subscopes[stmt.truepart.name] = stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars())
|
||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
||||
if(stmt.truepart.name==name) return stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||
}
|
||||
is IfStatement -> {
|
||||
subscopes[stmt.truepart.name] = stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars())
|
||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
||||
if(stmt.truepart.name==name) return stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||
}
|
||||
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 -> {}
|
||||
}
|
||||
}
|
||||
return subscopes
|
||||
return null
|
||||
}
|
||||
|
||||
fun getLabelOrVariable(name: String): Statement? {
|
||||
@ -121,7 +125,7 @@ interface INameScope {
|
||||
for(module in localContext.definingModule().program.modules) {
|
||||
var scope: INameScope? = module
|
||||
for(name in scopedName.dropLast(1)) {
|
||||
scope = scope?.subScopes()?.get(name)
|
||||
scope = scope?.subScope(name)
|
||||
if(scope==null)
|
||||
break
|
||||
}
|
||||
@ -129,7 +133,7 @@ interface INameScope {
|
||||
val result = scope.getLabelOrVariable(scopedName.last())
|
||||
if(result!=null)
|
||||
return result
|
||||
return scope.subScopes()[scopedName.last()] as Statement?
|
||||
return scope.subScope(scopedName.last()) as Statement?
|
||||
}
|
||||
}
|
||||
return null
|
||||
@ -141,7 +145,7 @@ interface INameScope {
|
||||
val result = localScope.getLabelOrVariable(scopedName[0])
|
||||
if (result != null)
|
||||
return result
|
||||
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
|
||||
val subscope = localScope.subScope(scopedName[0]) as Statement?
|
||||
if (subscope != null)
|
||||
return subscope
|
||||
// not found in this scope, look one higher up
|
||||
@ -175,6 +179,7 @@ interface INameScope {
|
||||
is ForeverLoop -> find(it.body)
|
||||
is WhileLoop -> find(it.body)
|
||||
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
|
||||
else -> { /* do nothing */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -191,7 +196,7 @@ interface IAssignable {
|
||||
|
||||
/*********** 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 definedLoadAddress: Int
|
||||
@ -206,11 +211,29 @@ class Program(val name: String, val modules: MutableList<Module>) {
|
||||
return if(mainBlocks.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
mainBlocks[0].subScopes()["start"] as Subroutine?
|
||||
mainBlocks[0].subScope("start") as Subroutine?
|
||||
}
|
||||
}
|
||||
|
||||
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.indexOf(node)
|
||||
modules[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
}
|
||||
|
||||
class Module(override val name: String,
|
||||
@ -218,6 +241,7 @@ class Module(override val name: String,
|
||||
override val position: Position,
|
||||
val isLibraryModule: Boolean,
|
||||
val source: Path) : Node, INameScope {
|
||||
|
||||
override lateinit var parent: Node
|
||||
lateinit var program: Program
|
||||
val importedBy = mutableListOf<Module>()
|
||||
@ -231,10 +255,21 @@ class Module(override val name: String,
|
||||
}
|
||||
|
||||
override fun definingScope(): INameScope = program.namespace
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node is Statement && replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
|
||||
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
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 {
|
||||
override val name = "<<<global>>>"
|
||||
override val position = Position("<<<global>>>", 0, 0, 0)
|
||||
@ -245,6 +280,10 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
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? {
|
||||
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
|
||||
// builtin functions always exist, return a dummy localContext for them
|
||||
@ -270,7 +309,7 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
|
||||
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
||||
null -> null
|
||||
else -> throw SyntaxError("wrong identifier target: $stmt", stmt.position)
|
||||
else -> throw SyntaxError("wrong identifier target for $scopedName: $stmt", stmt.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ enum class DataType {
|
||||
* is the type assignable to the given other type?
|
||||
*/
|
||||
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) {
|
||||
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||
@ -150,8 +150,15 @@ object ParentSentinel : Node {
|
||||
override val position = Position("<<sentinel>>", 0, 0, 0)
|
||||
override var parent: Node = this
|
||||
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) {
|
||||
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
|
||||
|
||||
companion object {
|
||||
val DUMMY = Position("<dummy>", 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
@ -8,13 +8,13 @@ class ErrorReporter {
|
||||
WARNING,
|
||||
ERROR
|
||||
}
|
||||
private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position?)
|
||||
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 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
|
||||
|
@ -4,17 +4,9 @@ import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.*
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
|
||||
|
||||
|
||||
// the name of the subroutine that should be called for every block to initialize its variables
|
||||
internal const val initvarsSubName="prog8_init_vars"
|
||||
|
||||
|
||||
internal fun Program.removeNopsFlattenAnonScopes() {
|
||||
val flattener = FlattenAnonymousScopesAndRemoveNops()
|
||||
flattener.visit(this)
|
||||
}
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
import prog8.optimizer.AssignmentTransformer
|
||||
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
|
||||
|
||||
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
|
||||
@ -22,29 +14,39 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: Err
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.anonscopeVarsCleanup(errors: ErrorReporter) {
|
||||
val mover = AnonymousScopeVarsCleanup(errors)
|
||||
mover.visit(this)
|
||||
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
|
||||
val fixer = BeforeAsmGenerationAstChanger(this, errors)
|
||||
fixer.visit(this)
|
||||
fixer.applyModifications()
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.reorderStatements() {
|
||||
val initvalueCreator = VarInitValueAndAddressOfCreator(this)
|
||||
initvalueCreator.visit(this)
|
||||
|
||||
val checker = StatementReorderer(this)
|
||||
checker.visit(this)
|
||||
val reorder = StatementReorderer(this)
|
||||
reorder.visit(this)
|
||||
reorder.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
val caster = TypecastsAdder(this, errors)
|
||||
caster.visit(this)
|
||||
caster.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Module.checkImportedValid(errors: ErrorReporter) {
|
||||
val checker = ImportedModuleDirectiveRemover(errors)
|
||||
checker.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() {
|
||||
val imr = ImportedModuleDirectiveRemover()
|
||||
imr.visit(this, this.parent)
|
||||
imr.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.checkRecursion(errors: ErrorReporter) {
|
||||
@ -53,18 +55,22 @@ internal fun Program.checkRecursion(errors: ErrorReporter) {
|
||||
checker.processMessages(name)
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||
val checker = AstIdentifiersChecker(this, errors)
|
||||
checker.visit(this)
|
||||
|
||||
if(modules.map {it.name}.toSet().size != modules.size) {
|
||||
if (modules.map { it.name }.toSet().size != modules.size) {
|
||||
throw FatalAstException("modules should all be unique")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun Program.makeForeverLoops() {
|
||||
val checker = MakeForeverLoops()
|
||||
val checker = ForeverLoopsMaker()
|
||||
checker.visit(this)
|
||||
checker.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.removeNopsFlattenAnonScopes() {
|
||||
val flattener = FlattenAnonymousScopesAndNopRemover()
|
||||
flattener.visit(this)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package prog8.ast.expressions
|
||||
import prog8.ast.*
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
@ -21,6 +22,7 @@ sealed class Expression: Node {
|
||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||
abstract fun accept(visitor: IAstModifyingVisitor): Expression
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
|
||||
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||
|
||||
@ -56,9 +58,17 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
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 accept(visitor: IAstModifyingVisitor) = 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 inferType(program: Program): InferredTypes.InferredType {
|
||||
val inferred = expression.inferType(program)
|
||||
@ -96,6 +106,16 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
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 {
|
||||
return "[$left $operator $right]"
|
||||
}
|
||||
@ -105,6 +125,8 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 inferType(program: Program): InferredTypes.InferredType {
|
||||
val leftDt = left.inferType(program)
|
||||
@ -205,9 +227,20 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
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 accept(visitor: IAstModifyingVisitor) = 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 inferType(program: Program): InferredTypes.InferredType {
|
||||
@ -235,8 +268,16 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
expression.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===expression)
|
||||
expression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
@ -259,11 +300,18 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
||||
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 referencesIdentifiers(vararg name: String) = false
|
||||
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: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression(), IAssignable {
|
||||
@ -274,8 +322,16 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
this.addressExpression.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===addressExpression)
|
||||
addressExpression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
@ -325,11 +381,16 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
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 constValue(program: Program) = this
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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)"
|
||||
|
||||
@ -413,9 +474,15 @@ class StructLiteralValue(var values: List<Expression>,
|
||||
values.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
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: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
|
||||
|
||||
@ -436,10 +503,17 @@ class StringLiteralValue(val value: String,
|
||||
override fun linkParents(parent: Node) {
|
||||
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 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: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String = "'${escape(value)}'"
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STR)
|
||||
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
|
||||
@ -462,10 +536,20 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
this.parent = parent
|
||||
value.forEach {it.linkParents(this)}
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
val idx = value.indexOf(node)
|
||||
value[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
||||
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: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String = "$value"
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isUnknown) type else guessDatatype(program)
|
||||
|
||||
@ -544,9 +628,22 @@ class RangeExpr(var from: Expression,
|
||||
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 accept(visitor: IAstModifyingVisitor) = 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 inferType(program: Program): InferredTypes.InferredType {
|
||||
val fromDt=from.inferType(program)
|
||||
@ -618,9 +715,15 @@ class RegisterExpr(val register: Register, override val position: Position) : Ex
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
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: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
|
||||
override fun toString(): String {
|
||||
return "RegisterExpr(register=$register, pos=$position)"
|
||||
@ -645,6 +748,10 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
val node = program.namespace.lookup(nameInSource, this)
|
||||
?: throw UndefinedSymbolError(this)
|
||||
@ -663,14 +770,16 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 inferType(program: Program): InferredTypes.InferredType {
|
||||
val targetStmt = targetStatement(program.namespace)
|
||||
if(targetStmt is VarDecl) {
|
||||
return InferredTypes.knownFor(targetStmt.datatype)
|
||||
return if(targetStmt is VarDecl) {
|
||||
InferredTypes.knownFor(targetStmt.datatype)
|
||||
} else {
|
||||
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
|
||||
InferredTypes.InferredType.unknown()
|
||||
}
|
||||
}
|
||||
|
||||
@ -699,6 +808,16 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
args.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
if(node===target)
|
||||
target=replacement as IdentifierReference
|
||||
else {
|
||||
val idx = args.indexOf(node)
|
||||
args[idx] = replacement as Expression
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program) = constValue(program, true)
|
||||
|
||||
private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteralValue? {
|
||||
@ -737,6 +856,8 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 inferType(program: Program): InferredTypes.InferredType {
|
||||
|
@ -1,41 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Statement
|
||||
import prog8.ast.statements.VarDecl
|
||||
|
||||
class AnonymousScopeVarsCleanup(private val errors: ErrorReporter): IAstModifyingVisitor {
|
||||
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
|
||||
|
||||
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) {
|
||||
errors.err("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
|
||||
}
|
||||
}
|
||||
|
@ -21,9 +21,11 @@ internal class AstChecker(private val program: Program,
|
||||
val mainBlocks = program.modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
|
||||
if(mainBlocks.size>1)
|
||||
errors.err("more than one 'main' block", mainBlocks[0].position)
|
||||
if(mainBlocks.isEmpty())
|
||||
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
|
||||
|
||||
for(mainBlock in mainBlocks) {
|
||||
val startSub = mainBlock.subScopes()["start"] as? Subroutine
|
||||
val startSub = mainBlock.subScope("start") as? Subroutine
|
||||
if (startSub == null) {
|
||||
errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position)
|
||||
} else {
|
||||
@ -40,7 +42,6 @@ internal class AstChecker(private val program: Program,
|
||||
is VarDecl -> true
|
||||
is InlineAssembly -> true
|
||||
is INameScope -> true
|
||||
is VariableInitializationAssignment -> true
|
||||
is NopStatement -> true
|
||||
else -> false
|
||||
}
|
||||
@ -57,7 +58,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(irqBlocks.size>1)
|
||||
errors.err("more than one 'irq' block", irqBlocks[0].position)
|
||||
for(irqBlock in irqBlocks) {
|
||||
val irqSub = irqBlock.subScopes()["irq"] as? Subroutine
|
||||
val irqSub = irqBlock.subScope("irq") as? Subroutine
|
||||
if (irqSub != null) {
|
||||
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
|
||||
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
|
||||
@ -81,7 +82,7 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(returnStmt: Return) {
|
||||
val expectedReturnValues = returnStmt.definingSubroutine()?.returntypes ?: emptyList()
|
||||
if(expectedReturnValues.size>1) {
|
||||
throw AstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
|
||||
throw FatalAstException("cannot use a return with one value in a subroutine that has multiple return values: $returnStmt")
|
||||
}
|
||||
|
||||
if(expectedReturnValues.isEmpty() && returnStmt.value!=null) {
|
||||
@ -96,7 +97,7 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("return value type mismatch", returnStmt.value!!.position)
|
||||
} else {
|
||||
if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.STRUCT))
|
||||
errors.err("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position)
|
||||
errors.err("type $valueDt of return value doesn't match subroutine's return type ${expectedReturnValues[0]}", returnStmt.value!!.position)
|
||||
}
|
||||
}
|
||||
super.visit(returnStmt)
|
||||
@ -325,7 +326,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
|
||||
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) // TODO use callgraph?
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
|
||||
if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", repeatLoop.untilCondition.position)
|
||||
@ -333,7 +334,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop) {
|
||||
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
|
||||
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y")) // TODO use callgraph?
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
|
||||
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", whileLoop.condition.position)
|
||||
@ -356,17 +357,30 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val sourceIdent = assignment.value as? IdentifierReference
|
||||
val targetIdent = assignment.target.identifier
|
||||
if(sourceIdent!=null && targetIdent!=null) {
|
||||
val sourceVar = sourceIdent.targetVarDecl(program.namespace)
|
||||
if(targetIdent!=null) {
|
||||
val targetVar = targetIdent.targetVarDecl(program.namespace)
|
||||
if(sourceVar?.struct!=null && targetVar?.struct!=null) {
|
||||
if(sourceVar.struct!==targetVar.struct)
|
||||
errors.err("assignment of different struct types", assignment.position)
|
||||
if(targetVar?.struct != null) {
|
||||
val sourceStructLv = assignment.value as? StructLiteralValue
|
||||
if (sourceStructLv != null) {
|
||||
if (sourceStructLv.values.size != targetVar.struct?.numberOfElements)
|
||||
errors.err("number of elements doesn't match struct definition", sourceStructLv.position)
|
||||
} else {
|
||||
val sourceIdent = assignment.value as? IdentifierReference
|
||||
if (sourceIdent != null) {
|
||||
val sourceVar = sourceIdent.targetVarDecl(program.namespace)
|
||||
if (sourceVar?.struct != null) {
|
||||
if (sourceVar.struct !== targetVar.struct)
|
||||
errors.err("assignment of different struct types", assignment.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(assignment.value.inferType(program) != assignment.target.inferType(program, assignment))
|
||||
errors.err("assignment value is of different type as the target", assignment.value.position)
|
||||
|
||||
super.visit(assignment)
|
||||
}
|
||||
|
||||
@ -401,15 +415,14 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR)
|
||||
if(targetDt in IterableDatatypes)
|
||||
errors.err("cannot assign to a string or array type", assignTarget.position)
|
||||
|
||||
// target type check is already done at the Assignment:
|
||||
// val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR)
|
||||
// if(targetDt in IterableDatatypes)
|
||||
// errors.err("cannot assign to a string or array type", assignTarget.position)
|
||||
|
||||
if (assignment is Assignment) {
|
||||
|
||||
if (assignment.aug_op != null)
|
||||
throw FatalAstException("augmented assignment should have been converted into normal assignment")
|
||||
|
||||
val targetDatatype = assignTarget.inferType(program, assignment)
|
||||
if (targetDatatype.isKnown) {
|
||||
val constVal = assignment.value.constValue(program)
|
||||
@ -446,10 +459,11 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
fun err(msg: String, position: Position?=null) {
|
||||
err(msg, position ?: decl.position)
|
||||
errors.err(msg, position ?: decl.position)
|
||||
}
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO use callgraph for check?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
err("recursive var declaration")
|
||||
}
|
||||
@ -493,37 +507,12 @@ internal class AstChecker(private val program: Program,
|
||||
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE || decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
|
||||
err("struct can not be in zeropage")
|
||||
}
|
||||
if (decl.value == null) {
|
||||
when {
|
||||
decl.datatype in NumericDatatypes -> {
|
||||
// initialize numeric var with value zero by default.
|
||||
val litVal =
|
||||
when (decl.datatype) {
|
||||
in ByteDatatypes -> NumericLiteralValue(decl.datatype, 0, decl.position)
|
||||
in WordDatatypes -> NumericLiteralValue(decl.datatype, 0, decl.position)
|
||||
else -> NumericLiteralValue(decl.datatype, 0.0, decl.position)
|
||||
}
|
||||
litVal.parent = decl
|
||||
decl.value = litVal
|
||||
}
|
||||
decl.datatype == DataType.STRUCT -> {
|
||||
// structs may be initialized with an array, but it's okay to not initialize them as well.
|
||||
}
|
||||
decl.type == VarDeclType.VAR -> {
|
||||
if(decl.datatype in ArrayDatatypes) {
|
||||
// array declaration can have an optional initialization value
|
||||
// if it is absent, the size must be given, which should have been checked earlier
|
||||
if(decl.value==null && decl.arraysize==null)
|
||||
throw FatalAstException("array init check failed")
|
||||
}
|
||||
}
|
||||
else -> err("var/const declaration needs a compile-time constant initializer value for type ${decl.datatype}")
|
||||
// const fold should have provided it!
|
||||
}
|
||||
super.visit(decl)
|
||||
return
|
||||
}
|
||||
|
||||
when(decl.value) {
|
||||
null -> {
|
||||
// a vardecl without an initial value, don't bother with the rest
|
||||
return super.visit(decl)
|
||||
}
|
||||
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value")
|
||||
is StringLiteralValue -> {
|
||||
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
|
||||
@ -556,6 +545,8 @@ internal class AstChecker(private val program: Program,
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errors.err("struct literal is wrong type to initialize this variable", decl.value!!.position)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -593,6 +584,10 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR && !declValue.inferType(program).istype(decl.datatype))
|
||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
@ -719,9 +714,9 @@ internal class AstChecker(private val program: Program,
|
||||
override fun visit(expr: BinaryExpression) {
|
||||
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")
|
||||
}
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
return // hopefully this error will be detected elsewhere
|
||||
|
||||
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
|
||||
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
|
||||
|
||||
@ -851,7 +846,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
super.visit(functionCallStatement)
|
||||
@ -899,6 +894,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
} else if(target is Subroutine) {
|
||||
if(target.regXasResult())
|
||||
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
|
||||
if(args.size!=target.parameters.size)
|
||||
errors.err("invalid number of arguments", position)
|
||||
else {
|
||||
@ -917,7 +914,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(target.isAsmSubroutine) {
|
||||
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
|
||||
if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
|
||||
errors.warn("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position)
|
||||
errors.warn("calling a subroutine that expects X as a parameter is problematic. If you see a compiler error/crash about this later, try to change this call", position)
|
||||
}
|
||||
|
||||
// check if the argument types match the register(pairs)
|
||||
@ -1059,13 +1056,19 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
private fun visitStatements(statements: List<Statement>) {
|
||||
for((index, stmt) in statements.withIndex()) {
|
||||
if(stmt is FunctionCallStatement
|
||||
&& stmt.target.nameInSource.last()=="exit"
|
||||
&& index < statements.lastIndex)
|
||||
errors.warn("unreachable code, exit call above never returns", statements[index+1].position)
|
||||
|
||||
if(stmt is Return && index < statements.lastIndex)
|
||||
errors.warn("unreachable code, return statement above", statements[index+1].position)
|
||||
if(index < statements.lastIndex && statements[index+1] !is Subroutine) {
|
||||
when {
|
||||
stmt is FunctionCallStatement && stmt.target.nameInSource.last() == "exit" -> {
|
||||
errors.warn("unreachable code, preceding exit call will never return", statements[index + 1].position)
|
||||
}
|
||||
stmt is Return && statements[index + 1] !is Subroutine -> {
|
||||
errors.warn("unreachable code, preceding return statement", statements[index + 1].position)
|
||||
}
|
||||
stmt is Jump && statements[index + 1] !is Subroutine -> {
|
||||
errors.warn("unreachable code, preceding jump statement", statements[index + 1].position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stmt.accept(this)
|
||||
}
|
||||
@ -1265,7 +1268,7 @@ internal class AstChecker(private val program: Program,
|
||||
correct = array.all { it in -32768..32767 }
|
||||
}
|
||||
DataType.ARRAY_F -> correct = true
|
||||
else -> throw AstException("invalid array type $type")
|
||||
else -> throw FatalAstException("invalid array type $type")
|
||||
}
|
||||
if (!correct)
|
||||
errors.err("array value out of range for type $type", value.position)
|
||||
|
@ -10,7 +10,7 @@ import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
// TODO implement using AstWalker instead of IAstModifyingVisitor
|
||||
internal class AstIdentifiersChecker(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
@ -82,6 +82,11 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
if(decl.value != null && decl.value !is StructLiteralValue) {
|
||||
errors.err("initializing requires struct literal value", decl.value?.position ?: decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
val decls = decl.flattenStructMembers()
|
||||
decls.add(decl)
|
||||
val result = AnonymousScope(decls, decl.position)
|
||||
@ -142,8 +147,7 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
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)
|
||||
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||
vardecl.linkParents(subroutine)
|
||||
subroutine.statements.add(0, vardecl)
|
||||
}
|
||||
@ -213,19 +217,6 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
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) {
|
||||
|
@ -2,6 +2,7 @@ package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
@ -16,7 +17,7 @@ internal class AstRecursionChecker(private val namespace: INameScope,
|
||||
if(cycle.isEmpty())
|
||||
return
|
||||
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
|
||||
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", null)
|
||||
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) {
|
||||
|
454
compiler/src/prog8/ast/processing/AstWalker.kt
Normal file
454
compiler/src/prog8/ast/processing/AstWalker.kt
Normal file
@ -0,0 +1,454 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
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))
|
||||
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.indexOf(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(foreverLoop: ForeverLoop, 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(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(repeatLoop: RepeatLoop, 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(structLv: StructLiteralValue, 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(foreverLoop: ForeverLoop, 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(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(repeatLoop: RepeatLoop, 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(structLv: StructLiteralValue, 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(foreverLoop: ForeverLoop, parent: Node) {
|
||||
track(before(foreverLoop, parent), foreverLoop, parent)
|
||||
foreverLoop.body.accept(this, foreverLoop)
|
||||
track(after(foreverLoop, parent), foreverLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop, parent: Node) {
|
||||
track(before(repeatLoop, parent), repeatLoop, parent)
|
||||
repeatLoop.untilCondition.accept(this, repeatLoop)
|
||||
repeatLoop.body.accept(this, repeatLoop)
|
||||
track(after(repeatLoop, parent), repeatLoop, 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(registerExpr: RegisterExpr, parent: Node) {
|
||||
track(before(registerExpr, parent), registerExpr, parent)
|
||||
track(after(registerExpr, parent), registerExpr, 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)
|
||||
}
|
||||
|
||||
fun visit(structLv: StructLiteralValue, parent: Node) {
|
||||
track(before(structLv, parent), structLv, parent)
|
||||
structLv.values.forEach { it.accept(this, structLv) }
|
||||
track(after(structLv, parent), structLv, parent)
|
||||
}
|
||||
}
|
||||
|
28
compiler/src/prog8/ast/processing/ForeverLoopsMaker.kt
Normal file
28
compiler/src/prog8/ast/processing/ForeverLoopsMaker.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.ForeverLoop
|
||||
import prog8.ast.statements.RepeatLoop
|
||||
import prog8.ast.statements.WhileLoop
|
||||
|
||||
|
||||
internal class ForeverLoopsMaker: AstWalker() {
|
||||
override fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
val numeric = repeatLoop.untilCondition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() == 0) {
|
||||
val forever = ForeverLoop(repeatLoop.body, repeatLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(repeatLoop, forever, parent))
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val numeric = whileLoop.condition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() != 0) {
|
||||
val forever = ForeverLoop(whileLoop.body, whileLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
}
|
@ -6,10 +6,10 @@ import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
// TODO replace all occurrences of this with AstWalker
|
||||
interface IAstModifyingVisitor {
|
||||
fun visit(program: Program) {
|
||||
program.modules.forEach { visit(it) }
|
||||
program.modules.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(module: Module) {
|
||||
|
@ -7,7 +7,7 @@ import prog8.ast.statements.*
|
||||
|
||||
interface IAstVisitor {
|
||||
fun visit(program: Program) {
|
||||
program.modules.forEach { visit(it) }
|
||||
program.modules.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(module: Module) {
|
||||
|
@ -1,29 +1,20 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.Statement
|
||||
|
||||
internal class ImportedModuleDirectiveRemover(private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
|
||||
internal class ImportedModuleDirectiveRemover: AstWalker() {
|
||||
/**
|
||||
* 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")
|
||||
for (sourceStmt in module.statements) {
|
||||
val stmt = sourceStmt.accept(this)
|
||||
if(stmt is Directive && stmt.parent is Module) {
|
||||
if(stmt.directive in moduleLevelDirectives) {
|
||||
errors.warn("ignoring module directive because it was imported", stmt.position)
|
||||
continue
|
||||
}
|
||||
}
|
||||
newStatements.add(stmt)
|
||||
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||
|
||||
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
|
||||
if(directive.directive in moduleLevelDirectives) {
|
||||
return listOf(IAstModification.Remove(directive, parent))
|
||||
}
|
||||
module.statements = newStatements
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.ForeverLoop
|
||||
import prog8.ast.statements.RepeatLoop
|
||||
import prog8.ast.statements.Statement
|
||||
import prog8.ast.statements.WhileLoop
|
||||
|
||||
internal class MakeForeverLoops : IAstModifyingVisitor {
|
||||
override fun visit(whileLoop: WhileLoop): Statement {
|
||||
val numeric = whileLoop.condition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() != 0) {
|
||||
return ForeverLoop(whileLoop.body, whileLoop.position)
|
||||
}
|
||||
return super.visit(whileLoop)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop): Statement {
|
||||
val numeric = repeatLoop.untilCondition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() == 0)
|
||||
return ForeverLoop(repeatLoop.body, repeatLoop.position)
|
||||
return super.visit(repeatLoop)
|
||||
}
|
||||
}
|
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,257 +1,184 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.initvarsSubName
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
private fun flattenStructAssignmentFromIdentifier(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!!
|
||||
when (structAssignment.value) {
|
||||
is IdentifierReference -> {
|
||||
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
if (sourceVar.struct == null)
|
||||
throw FatalAstException("can only assign arrays or structs to structs")
|
||||
// struct memberwise copy
|
||||
val sourceStruct = sourceVar.struct!!
|
||||
if(sourceStruct!==targetVar.struct) {
|
||||
// structs are not the same in assignment
|
||||
return listOf() // error will be printed elsewhere
|
||||
}
|
||||
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||
val targetDecl = member.first as VarDecl
|
||||
val sourceDecl = member.second as VarDecl
|
||||
if(targetDecl.name != sourceDecl.name)
|
||||
throw FatalAstException("struct member mismatch")
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
null, sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
is StructLiteralValue -> {
|
||||
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||
}
|
||||
else -> throw FatalAstException("strange struct value")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
|
||||
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.
|
||||
// - 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.
|
||||
// - 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 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)
|
||||
|
||||
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()
|
||||
|
||||
// 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)
|
||||
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)
|
||||
}
|
||||
|
||||
val varDecls = module.statements.filterIsInstance<VarDecl>()
|
||||
module.statements.removeAll(varDecls)
|
||||
module.statements.addAll(0, varDecls)
|
||||
reorderVardeclsAndDirectives(module.statements)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
|
||||
module.statements.removeAll(directives)
|
||||
module.statements.addAll(0, directives)
|
||||
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
|
||||
val varDecls = statements.filterIsInstance<VarDecl>()
|
||||
statements.removeAll(varDecls)
|
||||
statements.addAll(0, varDecls)
|
||||
|
||||
for(pos in addReturns) {
|
||||
val returnStmt = Return(null, pos.first.position)
|
||||
returnStmt.linkParents(pos.first as Node)
|
||||
pos.first.statements.add(pos.second, returnStmt)
|
||||
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 emptyList()
|
||||
}
|
||||
|
||||
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 emptyList()
|
||||
}
|
||||
|
||||
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(null, 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 emptyList()
|
||||
}
|
||||
|
||||
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 emptyList()
|
||||
}
|
||||
|
||||
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 StructLiteralValue) {
|
||||
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 emptyList()
|
||||
}
|
||||
|
||||
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? StructLiteralValue
|
||||
if(slv==null || slv.values.size != struct.numberOfElements)
|
||||
throw FatalAstException("element count mismatch")
|
||||
|
||||
return struct.statements.zip(slv.values).map { (targetDecl, sourceValue) ->
|
||||
targetDecl as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
null, sourceValue, sourceValue.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
private fun flattenStructAssignmentFromIdentifier(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!!
|
||||
when (structAssignment.value) {
|
||||
is IdentifierReference -> {
|
||||
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
if (sourceVar.struct == null)
|
||||
throw FatalAstException("can only assign arrays or structs to structs")
|
||||
// struct memberwise copy
|
||||
val sourceStruct = sourceVar.struct!!
|
||||
if(sourceStruct!==targetVar.struct) {
|
||||
// structs are not the same in assignment
|
||||
return listOf() // error will be printed elsewhere
|
||||
}
|
||||
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||
val targetDecl = member.first as VarDecl
|
||||
val sourceDecl = member.second as VarDecl
|
||||
if(targetDecl.name != sourceDecl.name)
|
||||
throw FatalAstException("struct member mismatch")
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
null, sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
is StructLiteralValue -> {
|
||||
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||
}
|
||||
else -> throw FatalAstException("strange struct value")
|
||||
}
|
||||
|
||||
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,82 +2,90 @@ package prog8.ast.processing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
internal class TypecastsAdder(private val program: Program,
|
||||
private val errors: ErrorReporter): IAstModifyingVisitor {
|
||||
// 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)
|
||||
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)
|
||||
*/
|
||||
|
||||
override fun visit(expr: BinaryExpression): Expression {
|
||||
val expr2 = super.visit(expr)
|
||||
if(expr2 !is BinaryExpression)
|
||||
return expr2
|
||||
val leftDt = expr2.left.inferType(program)
|
||||
val rightDt = expr2.right.inferType(program)
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
|
||||
// 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) {
|
||||
when {
|
||||
toFix===expr2.left -> {
|
||||
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
|
||||
expr2.left.linkParents(expr2)
|
||||
}
|
||||
toFix===expr2.right -> {
|
||||
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
|
||||
expr2.right.linkParents(expr2)
|
||||
}
|
||||
return when {
|
||||
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
|
||||
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr))
|
||||
toFix===expr.right -> listOf(IAstModification.ReplaceNode(
|
||||
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
|
||||
else -> throw FatalAstException("confused binary expression side")
|
||||
}
|
||||
}
|
||||
}
|
||||
return expr2
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
val assg = super.visit(assignment)
|
||||
if(assg !is Assignment)
|
||||
return assg
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// 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)
|
||||
|
||||
val valueItype = assignment.value.inferType(program)
|
||||
val targetItype = assignment.target.inferType(program, assignment)
|
||||
if(targetItype.isKnown && valueItype.isKnown) {
|
||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||
if (valuetype != targettype) {
|
||||
if (valuetype isAssignableTo targettype) {
|
||||
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
|
||||
assg.value.linkParents(assg)
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
||||
}
|
||||
}
|
||||
return assg
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
|
||||
return super.visit(functionCallStatement)
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope())
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
checkFunctionCallArguments(functionCall, functionCall.definingScope())
|
||||
return super.visit(functionCall)
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
return afterFunctionCallArgs(functionCall, functionCall.definingScope())
|
||||
}
|
||||
|
||||
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
|
||||
when(val sub = call.target.targetStatement(scope)) {
|
||||
return when(val sub = call.target.targetStatement(scope)) {
|
||||
is Subroutine -> {
|
||||
for(arg in sub.parameters.zip(call.args.withIndex())) {
|
||||
val argItype = arg.second.value.inferType(program)
|
||||
@ -86,14 +94,21 @@ internal class TypecastsAdder(private val program: Program,
|
||||
val requiredType = arg.first.type
|
||||
if (requiredType != argtype) {
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position)
|
||||
typecasted.linkParents(arg.second.value.parent)
|
||||
call.args[arg.second.index] = typecasted
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
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.
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||
call as Node))
|
||||
}
|
||||
// if they're not assignable, we'll get a proper error later from the AstChecker
|
||||
}
|
||||
}
|
||||
}
|
||||
emptyList()
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(sub.name)
|
||||
@ -107,93 +122,116 @@ internal class TypecastsAdder(private val program: Program,
|
||||
continue
|
||||
for (possibleType in arg.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position)
|
||||
typecasted.linkParents(arg.second.value.parent)
|
||||
call.args[arg.second.index] = typecasted
|
||||
break
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
|
||||
call as Node))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emptyList()
|
||||
}
|
||||
null -> {}
|
||||
null -> emptyList()
|
||||
else -> throw FatalAstException("call to something weird $sub ${call.target}")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
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 emptyList()
|
||||
}
|
||||
|
||||
override fun visit(memread: DirectMemoryRead): Expression {
|
||||
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val dt = memread.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||
val literaladdr = memread.addressExpression as? NumericLiteralValue
|
||||
if(literaladdr!=null) {
|
||||
memread.addressExpression = literaladdr.cast(DataType.UWORD)
|
||||
} else {
|
||||
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||
memread.addressExpression.parent = memread
|
||||
}
|
||||
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
|
||||
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||
}
|
||||
return super.visit(memread)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
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)
|
||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
|
||||
if(literaladdr!=null) {
|
||||
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
|
||||
} else {
|
||||
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||
memwrite.addressExpression.parent = memwrite
|
||||
}
|
||||
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
|
||||
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||
}
|
||||
super.visit(memwrite)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun visit(structLv: StructLiteralValue): Expression {
|
||||
val litval = super.visit(structLv)
|
||||
if(litval !is StructLiteralValue)
|
||||
return litval
|
||||
override fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
// assignment of a struct literal value, some member values may need proper typecast
|
||||
|
||||
val decl = litval.parent as? VarDecl
|
||||
fun addTypecastsIfNeeded(struct: StructDecl): Iterable<IAstModification> {
|
||||
val newValues = struct.statements.zip(structLv.values).map { (structMemberDecl, memberValue) ->
|
||||
val memberDt = (structMemberDecl as VarDecl).datatype
|
||||
val valueDt = memberValue.inferType(program)
|
||||
if (valueDt.typeOrElse(memberDt) != memberDt)
|
||||
TypecastExpression(memberValue, memberDt, true, memberValue.position)
|
||||
else
|
||||
memberValue
|
||||
}
|
||||
|
||||
class StructLvValueReplacer(val targetStructLv: StructLiteralValue, val typecastValues: List<Expression>) : IAstModification {
|
||||
override fun perform() {
|
||||
targetStructLv.values = typecastValues
|
||||
typecastValues.forEach { it.linkParents(targetStructLv) }
|
||||
}
|
||||
}
|
||||
|
||||
return if(structLv.values.zip(newValues).any { (v1, v2) -> v1 !== v2})
|
||||
listOf(StructLvValueReplacer(structLv, newValues))
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val decl = structLv.parent as? VarDecl
|
||||
if(decl != null) {
|
||||
val struct = decl.struct
|
||||
if(struct != null) {
|
||||
addTypecastsIfNeeded(litval, struct)
|
||||
}
|
||||
if(struct != null)
|
||||
return addTypecastsIfNeeded(struct)
|
||||
} else {
|
||||
val assign = litval.parent as? Assignment
|
||||
val assign = structLv.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)
|
||||
}
|
||||
if(struct != null)
|
||||
return addTypecastsIfNeeded(struct)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return litval
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
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
|
||||
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 emptyList()
|
||||
if (returnValue is NumericLiteralValue) {
|
||||
returnStmt.value = returnValue.cast(subroutine.returntypes.single())
|
||||
} else {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
returnValue,
|
||||
TypecastExpression(returnValue, subReturnType, true, returnValue.position),
|
||||
returnStmt))
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
|
@ -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.FSignature
|
||||
|
||||
|
||||
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(InferredTypes.InferredType.known(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: FSignature, 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)
|
||||
}
|
||||
|
||||
}
|
@ -3,6 +3,7 @@ package prog8.ast.statements
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
|
||||
@ -10,6 +11,8 @@ import prog8.ast.processing.IAstVisitor
|
||||
sealed class Statement : Node {
|
||||
abstract fun accept(visitor: IAstModifyingVisitor) : Statement
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
|
||||
fun makeScopedName(name: String): String {
|
||||
// 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.
|
||||
@ -37,12 +40,17 @@ sealed class Statement : Node {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
|
||||
override var parent: Node = ParentSentinel
|
||||
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: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
replacement.parent = this
|
||||
}
|
||||
override val expensiveToInline = false
|
||||
}
|
||||
|
||||
@ -62,8 +70,16 @@ class Block(override val name: String,
|
||||
statements.forEach {it.linkParents(this)}
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 {
|
||||
return "Block(name=$name, address=$address, ${statements.size} statements)"
|
||||
@ -81,8 +97,10 @@ data class Directive(val directive: String, val args: List<DirectiveArg>, overri
|
||||
args.forEach{it.linkParents(this)}
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 {
|
||||
@ -91,6 +109,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
|
||||
override fun linkParents(parent: Node) {
|
||||
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() {
|
||||
@ -101,8 +120,10 @@ data class Label(val name: String, override val position: Position) : Statement(
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 {
|
||||
return "Label(name=$name, pos=$position)"
|
||||
@ -118,8 +139,15 @@ open class Return(var value: Expression?, override val position: Position) : Sta
|
||||
value?.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
value = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 {
|
||||
return "Return($value, pos=$position)"
|
||||
@ -133,6 +161,7 @@ class ReturnFromIrq(override val position: Position) : Return(null, position) {
|
||||
override fun toString(): String {
|
||||
return "ReturnFromIrq(pos=$position)"
|
||||
}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
class Continue(override val position: Position) : Statement() {
|
||||
@ -143,8 +172,10 @@ class Continue(override val position: Position) : Statement() {
|
||||
this.parent=parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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() {
|
||||
@ -155,8 +186,10 @@ class Break(override val position: Position) : Statement() {
|
||||
this.parent=parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
|
||||
@ -167,7 +200,8 @@ enum class ZeropageWish {
|
||||
NOT_IN_ZEROPAGE
|
||||
}
|
||||
|
||||
class VarDecl(val type: VarDeclType,
|
||||
|
||||
open class VarDecl(val type: VarDeclType,
|
||||
private val declaredDatatype: DataType,
|
||||
val zeropage: ZeropageWish,
|
||||
var arraysize: ArrayIndex?,
|
||||
@ -204,6 +238,15 @@ class VarDecl(val type: VarDeclType,
|
||||
return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
|
||||
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
|
||||
@ -232,29 +275,21 @@ class VarDecl(val type: VarDeclType,
|
||||
}
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===value)
|
||||
value = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
|
||||
val scopedname: String by lazy { makeScopedName(name) }
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
|
||||
}
|
||||
|
||||
fun asDefaultValueDecl(parent: Node?): VarDecl {
|
||||
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 zeroElementValue() = defaultZero(declaredDatatype, position)
|
||||
|
||||
fun flattenStructMembers(): MutableList<Statement> {
|
||||
val result = struct!!.statements.withIndex().map {
|
||||
@ -278,6 +313,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 {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -286,6 +326,12 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
||||
index.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===index)
|
||||
index = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun forArray(v: ArrayLiteralValue): ArrayIndex {
|
||||
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
|
||||
@ -295,10 +341,8 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
||||
fun accept(visitor: IAstModifyingVisitor) {
|
||||
index = index.accept(visitor)
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) {
|
||||
index.accept(visitor)
|
||||
}
|
||||
fun accept(visitor: IAstVisitor) = index.accept(visitor)
|
||||
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return("ArrayIndex($index, pos=$position)")
|
||||
@ -307,7 +351,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
||||
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 val expensiveToInline
|
||||
get() = value !is NumericLiteralValue
|
||||
@ -318,18 +362,47 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
|
||||
value.linkParents(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: IAstModifyingVisitor) = 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 {
|
||||
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
|
||||
}
|
||||
}
|
||||
|
||||
// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope,
|
||||
// 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)
|
||||
fun asDesugaredNonaugmented(): Assignment {
|
||||
val augmented = aug_op ?: return this
|
||||
|
||||
val leftOperand: Expression =
|
||||
when {
|
||||
target.register != null -> RegisterExpr(target.register!!, target.position)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
data class AssignTarget(val register: Register?,
|
||||
var identifier: IdentifierReference?,
|
||||
@ -345,8 +418,18 @@ data class AssignTarget(val register: Register?,
|
||||
memoryAddress?.linkParents(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: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
companion object {
|
||||
fun fromExpr(expr: Expression): AssignTarget {
|
||||
@ -381,7 +464,13 @@ data class AssignTarget(val register: Register?,
|
||||
|
||||
infix fun isSameAs(value: Expression): Boolean {
|
||||
return when {
|
||||
this.memoryAddress!=null -> false
|
||||
this.memoryAddress!=null -> {
|
||||
// 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.register!=null -> value is RegisterExpr && value.register==register
|
||||
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
|
||||
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
||||
@ -443,8 +532,15 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
|
||||
target.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AssignTarget && node===target)
|
||||
target = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 {
|
||||
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
|
||||
@ -463,8 +559,10 @@ class Jump(val address: Int?,
|
||||
identifier?.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 {
|
||||
return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
|
||||
@ -485,8 +583,19 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
args.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
if(node===target)
|
||||
target = replacement as IdentifierReference
|
||||
else {
|
||||
val idx = args.indexOf(node)
|
||||
args[idx] = replacement as Expression
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 {
|
||||
return "FunctionCallStatement(target=$target, pos=$position)"
|
||||
@ -501,8 +610,10 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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>,
|
||||
@ -526,8 +637,16 @@ class AnonymousScope(override var statements: MutableList<Statement>,
|
||||
statements.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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() {
|
||||
@ -538,8 +657,10 @@ class NopStatement(override val position: Position): Statement() {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 {
|
||||
@ -580,13 +701,23 @@ class Subroutine(override val name: String,
|
||||
statements.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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 {
|
||||
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
|
||||
.asSequence()
|
||||
.filter { it is InlineAssembly }
|
||||
@ -603,6 +734,10 @@ open class SubroutineParameter(val name: String,
|
||||
override fun linkParents(parent: Node) {
|
||||
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,
|
||||
@ -620,8 +755,20 @@ class IfStatement(var condition: Expression,
|
||||
elsepart.linkParents(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: IAstModifyingVisitor) = 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,
|
||||
@ -638,8 +785,19 @@ class BranchStatement(var condition: BranchCondition,
|
||||
elsepart.linkParents(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: IAstModifyingVisitor) = 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?,
|
||||
@ -657,8 +815,19 @@ class ForLoop(val loopRegister: Register?,
|
||||
body.linkParents(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: IAstModifyingVisitor) = 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 {
|
||||
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
|
||||
@ -683,8 +852,18 @@ class WhileLoop(var condition: Expression,
|
||||
body.linkParents(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: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class ForeverLoop(var body: AnonymousScope, override val position: Position) : Statement() {
|
||||
@ -696,8 +875,15 @@ class ForeverLoop(var body: AnonymousScope, override val position: Position) : S
|
||||
body.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AnonymousScope && node===body)
|
||||
body = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = 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,
|
||||
@ -712,8 +898,18 @@ class RepeatLoop(var body: AnonymousScope,
|
||||
body.linkParents(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: IAstModifyingVisitor) = 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,
|
||||
@ -728,6 +924,16 @@ class WhenStatement(var condition: Expression,
|
||||
choices.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
if(node===condition)
|
||||
condition = replacement as Expression
|
||||
else {
|
||||
val idx = choices.indexOf(node)
|
||||
choices[idx] = replacement as WhenChoice
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
|
||||
// only gives sensible results when the choices are all valid (constant integers)
|
||||
val result = mutableListOf<Pair<List<Int>?, WhenChoice>>()
|
||||
@ -747,6 +953,7 @@ class WhenStatement(var condition: Expression,
|
||||
|
||||
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
|
||||
@ -760,12 +967,19 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
|
||||
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 {
|
||||
return "Choice($values at $position)"
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
|
||||
@ -781,11 +995,19 @@ class StructDecl(override val name: String,
|
||||
this.statements.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
val numberOfElements: Int
|
||||
get() = this.statements.size
|
||||
|
||||
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
|
||||
}
|
||||
@ -798,10 +1020,17 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
|
||||
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 {
|
||||
return "DirectMemoryWrite($addressExpression)"
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
104
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
104
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
@ -0,0 +1,104 @@
|
||||
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() {
|
||||
|
||||
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 emptyList()
|
||||
}
|
||||
|
||||
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(null, 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 emptyList()
|
||||
}
|
||||
|
||||
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 emptyList()
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.optimizer.UnusedCodeRemover
|
||||
import prog8.optimizer.constantFold
|
||||
import prog8.optimizer.optimizeStatements
|
||||
import prog8.optimizer.simplifyExpressions
|
||||
@ -25,107 +26,31 @@ fun compileProgram(filepath: Path,
|
||||
optimize: Boolean,
|
||||
writeAssembly: Boolean,
|
||||
outputDir: Path): CompilationResult {
|
||||
var programName = ""
|
||||
lateinit var programAst: Program
|
||||
var programName: String? = null
|
||||
|
||||
var importedFiles: List<Path> = emptyList()
|
||||
var success=false
|
||||
|
||||
lateinit var importedFiles: List<Path>
|
||||
val errors = ErrorReporter()
|
||||
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val importer = ModuleImporter(errors)
|
||||
errors.handle()
|
||||
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
|
||||
programAst = ast
|
||||
importedFiles = imported
|
||||
processAst(programAst, errors, compilationOptions)
|
||||
if (optimize)
|
||||
optimizeAst(programAst, errors)
|
||||
postprocessAst(programAst, errors, compilationOptions)
|
||||
|
||||
println("Parsing...")
|
||||
programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||
importer.importModule(programAst, filepath)
|
||||
errors.handle()
|
||||
// printAst(programAst) // TODO
|
||||
|
||||
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()
|
||||
|
||||
// perform initial syntax checks and constant folding
|
||||
println("Syntax check...")
|
||||
val time1 = measureTimeMillis {
|
||||
programAst.checkIdentifiers(errors)
|
||||
errors.handle()
|
||||
programAst.makeForeverLoops()
|
||||
}
|
||||
|
||||
//println(" time1: $time1")
|
||||
val time2 = measureTimeMillis {
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
}
|
||||
//println(" time2: $time2")
|
||||
val time3 = measureTimeMillis {
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.reorderStatements()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
}
|
||||
//println(" time3: $time3")
|
||||
val time4 = measureTimeMillis {
|
||||
programAst.checkValid(compilerOptions, errors) // check if tree is valid
|
||||
errors.handle()
|
||||
}
|
||||
//println(" time4: $time4")
|
||||
|
||||
programAst.checkIdentifiers(errors)
|
||||
errors.handle()
|
||||
|
||||
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(errors)
|
||||
errors.handle()
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.checkValid(compilerOptions, errors) // check if final tree is valid
|
||||
errors.handle()
|
||||
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
|
||||
errors.handle()
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
if(writeAssembly) {
|
||||
// asm generation directly from the Ast, no need for intermediate code
|
||||
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
|
||||
programAst.anonscopeVarsCleanup(errors)
|
||||
errors.handle()
|
||||
val assembly = CompilationTarget.asmGenerator(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize)
|
||||
assembly.assemble(compilerOptions)
|
||||
programName = assembly.name
|
||||
}
|
||||
success = true
|
||||
if(writeAssembly)
|
||||
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
|
||||
}
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||
return CompilationResult(true, programAst, programName, importedFiles)
|
||||
|
||||
} catch (px: ParsingFailedError) {
|
||||
System.err.print("\u001b[91m") // bright red
|
||||
@ -148,16 +73,35 @@ fun compileProgram(filepath: Path,
|
||||
System.out.flush()
|
||||
throw x
|
||||
}
|
||||
return CompilationResult(success, programAst, programName ?: "", importedFiles)
|
||||
|
||||
return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList())
|
||||
}
|
||||
|
||||
fun printAst(programAst: Program) {
|
||||
println()
|
||||
val printer = AstToSourceCode(::print, programAst)
|
||||
printer.visit(programAst)
|
||||
println()
|
||||
}
|
||||
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
|
||||
println("Parsing...")
|
||||
val importer = ModuleImporter(errors)
|
||||
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||
importer.importModule(programAst, filepath)
|
||||
errors.handle()
|
||||
|
||||
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
|
||||
|
||||
val compilerOptions = determineCompilationOptions(programAst)
|
||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||
|
||||
// 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 {
|
||||
val mainModule = program.modules.first()
|
||||
@ -194,3 +138,79 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
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.makeForeverLoops()
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.reorderStatements()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
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.removeNopsFlattenAnonScopes()
|
||||
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()
|
||||
}
|
||||
|
||||
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) // TODO
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
if(position!=null)
|
||||
errors.warn("allocated a large value (float) in zeropage", position)
|
||||
else
|
||||
errors.warn("$scopedname: allocated a large value (float) in zeropage", null)
|
||||
errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY)
|
||||
5
|
||||
} else throw CompilerException("floating point option not enabled")
|
||||
}
|
||||
|
@ -1,16 +1,18 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
internal interface CompilationTarget {
|
||||
companion object {
|
||||
lateinit var name: String
|
||||
lateinit var machine: IMachineDefinition
|
||||
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -26,9 +26,10 @@ import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
internal class AsmGen(private val program: Program,
|
||||
private val zeropage: Zeropage,
|
||||
private val options: CompilationOptions,
|
||||
private val outputDir: Path): IAssemblyGenerator {
|
||||
private val errors: ErrorReporter,
|
||||
private val zeropage: Zeropage,
|
||||
private val options: CompilationOptions,
|
||||
private val outputDir: Path): IAssemblyGenerator {
|
||||
|
||||
private val assemblyLines = mutableListOf<String>()
|
||||
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
@ -38,10 +39,11 @@ internal class AsmGen(private val program: Program,
|
||||
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
|
||||
private val postincrdecrAsmGen = PostIncrDecrAsmGen(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)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
internal val loopContinueLabels = ArrayDeque<String>()
|
||||
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
|
||||
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
|
||||
assemblyLines.clear()
|
||||
@ -125,11 +127,10 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
out(" ldx #\$ff\t; init estack pointer")
|
||||
|
||||
out(" ; initialize the variables in each block")
|
||||
for (block in program.allBlocks()) {
|
||||
val initVarsSub = block.statements.singleOrNull { it is Subroutine && it.name == initvarsSubName }
|
||||
if(initVarsSub!=null)
|
||||
out(" jsr ${block.name}.$initvarsSubName")
|
||||
out(" ; initialize the variables in each block that has globals")
|
||||
program.allBlocks().forEach {
|
||||
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
|
||||
out(" jsr ${it.name}.prog8_init_vars")
|
||||
}
|
||||
|
||||
out(" clc")
|
||||
@ -175,6 +176,21 @@ internal class AsmGen(private val program: Program,
|
||||
stmts.forEach { translate(it) }
|
||||
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(null, 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")
|
||||
}
|
||||
|
||||
@ -220,7 +236,7 @@ internal class AsmGen(private val program: Program,
|
||||
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||
for(variable in variables) {
|
||||
// should NOT allocate subroutine parameters on the zero page
|
||||
val fullName = variable.scopedname
|
||||
val fullName = variable.makeScopedName(variable.name)
|
||||
val zpVar = allocatedZeropageVariables[fullName]
|
||||
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)
|
||||
@ -296,7 +312,14 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
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 number = (it as NumericLiteralValue).number
|
||||
makeFloatFill(C64MachineDefinition.Mflpt5.fromNumber(number))
|
||||
@ -350,7 +373,7 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
// non-string variables
|
||||
normalVars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
|
||||
if(it.scopedname !in allocatedZeropageVariables)
|
||||
if(it.makeScopedName(it.name) !in allocatedZeropageVariables)
|
||||
vardecl2asm(it)
|
||||
}
|
||||
}
|
||||
@ -364,7 +387,14 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
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) {
|
||||
DataType.ARRAY_UB ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
@ -384,17 +414,22 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun makeArrayFillDataSigned(decl: VarDecl): List<String> {
|
||||
val array = (decl.value as ArrayLiteralValue).value
|
||||
|
||||
return when {
|
||||
decl.datatype == DataType.ARRAY_UB ->
|
||||
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) {
|
||||
DataType.ARRAY_UB ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
array.map {
|
||||
val number = (it as NumericLiteralValue).number.toInt()
|
||||
val hexnum = number.toString(16).padStart(2, '0')
|
||||
"$$hexnum"
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
}
|
||||
decl.datatype == DataType.ARRAY_B ->
|
||||
DataType.ARRAY_B ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
array.map {
|
||||
val number = (it as NumericLiteralValue).number.toInt()
|
||||
@ -404,12 +439,11 @@ internal class AsmGen(private val program: Program,
|
||||
else
|
||||
"-$$hexnum"
|
||||
}
|
||||
decl.datatype== DataType.ARRAY_UW -> array.map {
|
||||
DataType.ARRAY_UW -> array.map {
|
||||
val number = (it as NumericLiteralValue).number.toInt()
|
||||
val hexnum = number.toString(16).padStart(4, '0')
|
||||
"$$hexnum"
|
||||
"$" + number.toString(16).padStart(4, '0')
|
||||
}
|
||||
decl.datatype== DataType.ARRAY_W -> array.map {
|
||||
DataType.ARRAY_W -> array.map {
|
||||
val number = (it as NumericLiteralValue).number.toInt()
|
||||
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
|
||||
if(number>=0)
|
||||
@ -571,7 +605,9 @@ internal class AsmGen(private val program: Program,
|
||||
internal fun translate(stmt: Statement) {
|
||||
outputSourceLine(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 Return -> translate(stmt)
|
||||
is Subroutine -> translateSubroutine(stmt)
|
||||
@ -610,7 +646,7 @@ internal class AsmGen(private val program: Program,
|
||||
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
|
||||
is AnonymousScope -> translate(stmt)
|
||||
is Block -> throw AssemblyError("block should have been handled elsewhere")
|
||||
else -> TODO("no translation for $stmt")
|
||||
else -> throw AssemblyError("missing asm translation for $stmt")
|
||||
}
|
||||
}
|
||||
|
||||
@ -789,6 +825,27 @@ 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 target = AssignTarget(null, 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) {
|
||||
when(stmt.directive) {
|
||||
"%asminclude" -> {
|
||||
@ -857,6 +914,7 @@ internal class AsmGen(private val program: Program,
|
||||
val indexName = asmIdentifierName(index)
|
||||
out(" lda $indexName")
|
||||
}
|
||||
// TODO optimize more cases
|
||||
else -> {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
@ -864,6 +922,28 @@ 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 RegisterExpr -> {
|
||||
when (index.register) {
|
||||
Register.A -> out(" tay")
|
||||
Register.X -> out(" txa | tay")
|
||||
Register.Y -> {}
|
||||
}
|
||||
}
|
||||
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) =
|
||||
expressionsAsmGen.translateExpression(expression)
|
||||
|
||||
|
@ -13,43 +13,45 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
||||
|
||||
var linesByFour = getLinesBy(lines, 4)
|
||||
|
||||
var removeLines = optimizeUselessStackByteWrites(linesByFour)
|
||||
if(removeLines.isNotEmpty()) {
|
||||
for (i in removeLines.reversed())
|
||||
lines.removeAt(i)
|
||||
var mods = optimizeUselessStackByteWrites(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
removeLines = optimizeIncDec(linesByFour)
|
||||
if(removeLines.isNotEmpty()) {
|
||||
for (i in removeLines.reversed())
|
||||
lines.removeAt(i)
|
||||
mods = optimizeIncDec(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
removeLines = optimizeCmpSequence(linesByFour)
|
||||
if(removeLines.isNotEmpty()) {
|
||||
for (i in removeLines.reversed())
|
||||
lines.removeAt(i)
|
||||
mods = optimizeCmpSequence(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
removeLines = optimizeStoreLoadSame(linesByFour)
|
||||
if(removeLines.isNotEmpty()) {
|
||||
for (i in removeLines.reversed())
|
||||
lines.removeAt(i)
|
||||
mods = optimizeStoreLoadSame(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods= optimizeJsrRts(linesByFour)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
var linesByFourteen = getLinesBy(lines, 14)
|
||||
removeLines = optimizeSameAssignments(linesByFourteen)
|
||||
if(removeLines.isNotEmpty()) {
|
||||
for (i in removeLines.reversed())
|
||||
lines.removeAt(i)
|
||||
mods = optimizeSameAssignments(linesByFourteen)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFourteen = getLinesBy(lines, 14)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
@ -59,7 +61,22 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
|
||||
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:
|
||||
// lda $ce01,x
|
||||
// cmp #$20
|
||||
@ -68,42 +85,42 @@ fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int
|
||||
// cmp #$21
|
||||
// beq check_prog8_s73choice_33
|
||||
// the repeated lda can be removed
|
||||
val removeLines = mutableListOf<Int>()
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
|
||||
lines[1].value.trim().startsWith("cmp ") &&
|
||||
lines[2].value.trim().startsWith("beq ") &&
|
||||
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
|
||||
// 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) {
|
||||
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
|
||||
lines[1].value.trim()=="dex" &&
|
||||
lines[2].value.trim()=="inx" &&
|
||||
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
|
||||
removeLines.add(lines[1].index)
|
||||
removeLines.add(lines[2].index)
|
||||
removeLines.add(lines[3].index)
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
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)
|
||||
// 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...
|
||||
|
||||
val removeLines = mutableListOf<Int>()
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFourteen) {
|
||||
val first = pair[0].value.trimStart()
|
||||
val second = pair[1].value.trimStart()
|
||||
@ -122,8 +139,8 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
||||
val fourthvalue = sixth.substring(4)
|
||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||
removeLines.add(pair[4].index)
|
||||
removeLines.add(pair[5].index)
|
||||
mods.add(Modification(pair[4].index, true, null))
|
||||
mods.add(Modification(pair[5].index, true, null))
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +149,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
||||
val secondvalue = third.substring(4)
|
||||
if(firstvalue==secondvalue) {
|
||||
// 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)) {
|
||||
// identical float init
|
||||
removeLines.add(pair[7].index)
|
||||
removeLines.add(pair[8].index)
|
||||
removeLines.add(pair[9].index)
|
||||
removeLines.add(pair[10].index)
|
||||
mods.add(Modification(pair[7].index, true, null))
|
||||
mods.add(Modification(pair[8].index, true, null))
|
||||
mods.add(Modification(pair[9].index, true, null))
|
||||
mods.add(Modification(pair[10].index, true, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return removeLines
|
||||
return mods
|
||||
}
|
||||
|
||||
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 optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
|
||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// 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) {
|
||||
val first = pair[0].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 secondLoc = second.substring(4)
|
||||
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.
|
||||
val removeLines = mutableListOf<Int>()
|
||||
for (pair in linesByTwo) {
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
val first = pair[0].value
|
||||
val second = pair[1].value
|
||||
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)
|
||||
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|
||||
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
|
||||
removeLines.add(pair[0].index)
|
||||
removeLines.add(pair[1].index)
|
||||
mods.add(Modification(pair[0].index, true, null))
|
||||
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
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
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_PLUS1_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
@ -27,7 +28,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
is RegisterExpr -> translateExpression(expression)
|
||||
is IdentifierReference -> 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 assignment")
|
||||
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")
|
||||
}
|
||||
@ -39,8 +40,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
if (builtinFunc != null) {
|
||||
asmgen.translateFunctioncallExpression(expression, builtinFunc)
|
||||
} else {
|
||||
asmgen.translateFunctionCall(expression)
|
||||
val sub = expression.target.targetSubroutine(program.namespace)!!
|
||||
asmgen.translateFunctionCall(expression)
|
||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||
for ((_, reg) in returns) {
|
||||
if (!reg.stack) {
|
||||
@ -50,7 +51,18 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
RegisterOrPair.A -> asmgen.out(" 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.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
|
||||
@ -109,7 +121,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
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")
|
||||
}
|
||||
}
|
||||
@ -126,8 +138,16 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
asmgen.out(" lda ${address.toHex()} | sta $ESTACK_LO_HEX,x | dex")
|
||||
}
|
||||
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)
|
||||
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 -> {
|
||||
translateExpression(expr.addressExpression)
|
||||
@ -201,10 +221,36 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
translateExpression(expr.left)
|
||||
val amount = expr.right.constValue(program)!!.number.toInt()
|
||||
when (leftDt) {
|
||||
DataType.UBYTE -> repeat(amount) { asmgen.out(" lsr $ESTACK_LO_PLUS1_HEX,x") }
|
||||
DataType.BYTE -> repeat(amount) { asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||
DataType.UWORD -> repeat(amount) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $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") }
|
||||
DataType.UBYTE -> {
|
||||
if(amount<=2)
|
||||
repeat(amount) { asmgen.out(" lsr $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 -> {
|
||||
if(amount<=2)
|
||||
repeat(amount) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_uw_$amount") // 3-7 (8+ is done via other optimizations)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(amount<=2)
|
||||
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(" jsr math.shift_right_w_$amount") // 3-7 (8+ is done via other optimizations)
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
return
|
||||
@ -213,10 +259,22 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
// bit-shifts are always by a constant number (for now)
|
||||
translateExpression(expr.left)
|
||||
val amount = expr.right.constValue(program)!!.number.toInt()
|
||||
if (leftDt in ByteDatatypes)
|
||||
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x") }
|
||||
else
|
||||
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
|
||||
if (leftDt in ByteDatatypes) {
|
||||
if(amount<=2)
|
||||
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 {
|
||||
if(amount<=2) {
|
||||
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
|
||||
} else {
|
||||
asmgen.out(" jsr math.shift_left_w_$amount") // 3-7 (8+ is done via other optimizations)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
"*" -> {
|
||||
@ -273,8 +331,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
// the general, non-optimized cases
|
||||
translateExpression(expr.left)
|
||||
translateExpression(expr.right)
|
||||
if(leftDt!=rightDt)
|
||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always?
|
||||
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
||||
|
||||
when (leftDt) {
|
||||
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||
|
@ -19,7 +19,8 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// output the code to setup the parameters and perform the actual call
|
||||
// does NOT output the code to deal with the result values!
|
||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(Register.X in sub.asmClobbers)
|
||||
val saveX = Register.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...
|
||||
|
||||
val subName = asmgen.asmIdentifierName(stmt.target)
|
||||
@ -30,7 +31,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
asmgen.out(" jsr $subName")
|
||||
|
||||
if(Register.X in sub.asmClobbers)
|
||||
if(saveX)
|
||||
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
||||
}
|
||||
|
||||
@ -42,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
// pass parameter via a variable
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val paramVar = parameter.value
|
||||
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
||||
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
||||
@ -54,7 +55,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
|
||||
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")
|
||||
}
|
||||
}
|
||||
@ -64,7 +65,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
|
||||
in WordDatatypes -> asmgen.assignFromWordVariable(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")
|
||||
}
|
||||
}
|
||||
@ -202,11 +203,20 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.asmIdentifierName(value)
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||
else -> {}
|
||||
if(sourceDt 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) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -225,6 +235,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||
if(argType isAssignableTo paramType)
|
||||
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.
|
||||
// strings are assignable to UWORD, for example, and vice versa
|
||||
|
@ -66,7 +66,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
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")
|
||||
}
|
||||
@ -100,17 +104,14 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
else -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
|
156
compiler/src/prog8/optimizer/AssignmentTransformer.kt
Normal file
156
compiler/src/prog8/optimizer/AssignmentTransformer.kt
Normal file
@ -0,0 +1,156 @@
|
||||
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
|
||||
|
||||
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 emptyList()
|
||||
}
|
||||
}
|
||||
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 emptyList()
|
||||
}
|
||||
}
|
@ -6,8 +6,6 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ParentSentinel
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.base.initvarsSubName
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
@ -31,7 +29,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
|
||||
// TODO add dataflow graph: what statements use what variables
|
||||
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
|
||||
val usedSymbols = mutableSetOf<Statement>()
|
||||
|
||||
init {
|
||||
@ -120,7 +118,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|
||||
|| subroutine.name == initvarsSubName || subroutine.definingModule().isLibraryModule) {
|
||||
|| subroutine.definingModule().isLibraryModule) {
|
||||
// make sure the entrypoint is mentioned in the used symbols
|
||||
addNodeAndParentScopes(subroutine)
|
||||
}
|
||||
@ -128,8 +126,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
if (decl.autogeneratedDontRemove || (decl.definingModule().isLibraryModule && decl.type != VarDeclType.VAR)) {
|
||||
// make sure autogenerated vardecls are in the used symbols
|
||||
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
|
||||
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
|
||||
addNodeAndParentScopes(decl)
|
||||
}
|
||||
|
||||
|
@ -8,15 +8,15 @@ import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
class ConstantFolding(private val program: Program, private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
// TODO implement using AstWalker instead of IAstModifyingVisitor
|
||||
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call tree for this?
|
||||
// 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 decl
|
||||
@ -103,7 +103,7 @@ class ConstantFolding(private val program: Program, private val errors: ErrorRep
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
|
||||
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)
|
||||
decl.value = refValue
|
||||
refValue.parent=decl
|
||||
@ -140,6 +140,13 @@ class ConstantFolding(private val program: Program, private val errors: ErrorRep
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
decl.value = declValue.cast(decl.datatype)
|
||||
}
|
||||
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
@ -156,32 +163,22 @@ class ConstantFolding(private val program: Program, private val errors: ErrorRep
|
||||
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
|
||||
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
|
||||
}
|
||||
} catch (ax: AstException) {
|
||||
errors.err("unhandled AST error: $ax", identifier.position)
|
||||
identifier
|
||||
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
|
||||
else -> identifier
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
super.visit(functionCall)
|
||||
typeCastConstArguments(functionCall)
|
||||
return try {
|
||||
functionCall.constValue(program) ?: functionCall
|
||||
} catch (ax: AstException) {
|
||||
errors.err("unhandled AST error: $ax", functionCall.position)
|
||||
functionCall
|
||||
}
|
||||
return functionCall.constValue(program) ?: functionCall
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
@ -237,46 +234,41 @@ class ConstantFolding(private val program: Program, private val errors: ErrorRep
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
val prefixExpr=super.visit(expr)
|
||||
if(prefixExpr !is PrefixExpression)
|
||||
return prefixExpr
|
||||
} catch (ax: AstException) {
|
||||
errors.err("unhandled AST error: $ax", expr.position)
|
||||
expr
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
@ -297,45 +289,40 @@ class ConstantFolding(private val program: Program, private val errors: ErrorRep
|
||||
* (X + c1) - c2 -> X + (c1-c2)
|
||||
*/
|
||||
override fun visit(expr: BinaryExpression): Expression {
|
||||
return try {
|
||||
super.visit(expr)
|
||||
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")
|
||||
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 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
|
||||
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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
errors.err("unhandled AST error: $ax", expr.position)
|
||||
expr
|
||||
else -> expr
|
||||
}
|
||||
}
|
||||
|
||||
@ -633,72 +620,4 @@ class ConstantFolding(private val program: Program, private val errors: ErrorRep
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
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()
|
||||
|
||||
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 emptyList()
|
||||
}
|
||||
|
||||
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 emptyList()
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
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 emptyList()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when (targetDt) {
|
||||
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?)
|
||||
|
||||
}
|
@ -5,7 +5,7 @@ import prog8.ast.base.ErrorReporter
|
||||
|
||||
|
||||
internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
val optimizer = ConstantFolding(this, errors)
|
||||
val optimizer = ConstantFoldingOptimizer(this, errors)
|
||||
optimizer.visit(this)
|
||||
|
||||
while(errors.isEmpty() && optimizer.optimizationsDone>0) {
|
||||
@ -27,7 +27,7 @@ internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
|
||||
}
|
||||
|
||||
internal fun Program.simplifyExpressions() : Int {
|
||||
val optimizer = SimplifyExpressions(this)
|
||||
optimizer.visit(this)
|
||||
return optimizer.optimizationsDone
|
||||
val opti = ExpressionSimplifier(this)
|
||||
opti.visit(this)
|
||||
return opti.applyModifications()
|
||||
}
|
||||
|
@ -0,0 +1,46 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.NopStatement
|
||||
import prog8.ast.statements.Statement
|
||||
|
||||
internal class FlattenAnonymousScopesAndNopRemover: 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)
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
Investigate what optimizations binaryen has, 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: $assignment")
|
||||
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,8 +1,6 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
@ -20,6 +18,7 @@ import kotlin.math.floor
|
||||
*/
|
||||
|
||||
|
||||
// TODO implement using AstWalker instead of IAstModifyingVisitor
|
||||
internal class StatementOptimizer(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
@ -30,7 +29,6 @@ internal class StatementOptimizer(private val program: Program,
|
||||
private val vardeclsToRemove = mutableListOf<VarDecl>()
|
||||
|
||||
override fun visit(program: Program) {
|
||||
removeUnusedCode(callgraph)
|
||||
super.visit(program)
|
||||
|
||||
for(decl in vardeclsToRemove) {
|
||||
@ -38,45 +36,6 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
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 (block.containsNoCodeNorVars()) {
|
||||
@ -117,7 +76,6 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return NopStatement.insteadOf(subroutine)
|
||||
}
|
||||
|
||||
visitStatements(subroutine.statements)
|
||||
return subroutine
|
||||
}
|
||||
|
||||
@ -361,20 +319,19 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
private fun hasContinueOrBreak(scope: INameScope): Boolean {
|
||||
|
||||
class Searcher: IAstModifyingVisitor
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
var count=0
|
||||
|
||||
override fun visit(breakStmt: Break): Statement {
|
||||
override fun visit(breakStmt: Break) {
|
||||
count++
|
||||
return super.visit(breakStmt)
|
||||
}
|
||||
|
||||
override fun visit(contStmt: Continue): Statement {
|
||||
override fun visit(contStmt: Continue) {
|
||||
count++
|
||||
return super.visit(contStmt)
|
||||
}
|
||||
}
|
||||
|
||||
val s=Searcher()
|
||||
for(stmt in scope.statements) {
|
||||
stmt.accept(s)
|
||||
@ -410,7 +367,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
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: $assignment")
|
||||
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
if(assignment.target.isNotMemory(program.namespace)) {
|
||||
@ -550,7 +507,6 @@ internal class StatementOptimizer(private val program: Program,
|
||||
if(linesToRemove.isNotEmpty()) {
|
||||
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
|
||||
}
|
||||
visitStatements(scope.statements)
|
||||
return super.visit(scope)
|
||||
}
|
||||
|
||||
@ -563,58 +519,7 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
return super.visit(label)
|
||||
}
|
||||
|
||||
|
||||
private fun visitStatements(statements: MutableList<Statement>) {
|
||||
// TODO remove all unreachable code statements after call to exit() or a return
|
||||
// this is not yet correct because we still have nested subroutines
|
||||
// val exitCallIndex = statements.indexOfFirst { it is FunctionCallStatement && it.target.nameInSource.last()=="exit" }
|
||||
// if(exitCallIndex>=0) {
|
||||
// while(exitCallIndex < statements.lastIndex) {
|
||||
// val stmt = statements[exitCallIndex+1]
|
||||
// println("after exit() removing: $stmt")
|
||||
// statements.removeAt(exitCallIndex+1)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
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 && (sub.calledBy.isEmpty() || (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)) // TODO does removing modules work like this?
|
||||
}
|
||||
|
||||
return removals
|
||||
}
|
||||
}
|
@ -140,7 +140,7 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
importModule(program, modulePath)
|
||||
}
|
||||
|
||||
importedModule.checkImportedValid(errors)
|
||||
importedModule.checkImportedValid()
|
||||
return importedModule
|
||||
}
|
||||
|
||||
|
@ -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.*
|
||||
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 altEncoding: Boolean, val heapId: Int?): RuntimeValueBase(DataType.STR) {
|
||||
companion object {
|
||||
fun fromLv(string: StringLiteralValue): RuntimeValueString {
|
||||
return RuntimeValueString(string.value, string.altEncoding, 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.istype(DataType.ARRAY_F)) {
|
||||
val doubleArray = array.value.map { (it as NumericLiteralValue).number }.toTypedArray()
|
||||
RuntimeValueArray(DataType.ARRAY_F, 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.typeOrElse(DataType.STRUCT), 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, false), false, 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, altEncoding: Boolean) {
|
||||
val encoded = CompilationTarget.encodeString(str, altEncoding)
|
||||
var addr = address
|
||||
for (c in encoded) setUByte(addr++, c)
|
||||
setUByte(addr, 0)
|
||||
}
|
||||
|
||||
fun getString(strAddress: Int, altEncoding: Boolean): 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, altEncoding)
|
||||
}
|
||||
|
||||
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.*
|
||||
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)
|
||||
}
|
||||
}
|
@ -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())
|
||||
}
|
||||
}
|
@ -16,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.Mflpt5
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.vm.RuntimeValueNumeric
|
||||
import java.io.CharConversionException
|
||||
import kotlin.test.*
|
||||
|
||||
@ -356,8 +355,8 @@ class TestPetscii {
|
||||
|
||||
@Test
|
||||
fun testLiteralValueComparisons() {
|
||||
val ten = NumericLiteralValue(DataType.UWORD, 10, Position("", 0, 0, 0))
|
||||
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position("", 0, 0, 0))
|
||||
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
|
||||
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
|
||||
assertEquals(ten, ten)
|
||||
assertNotEquals(ten, nine)
|
||||
assertFalse(ten != ten)
|
||||
@ -373,30 +372,10 @@ class TestPetscii {
|
||||
assertTrue(ten <= ten)
|
||||
assertFalse(ten < ten)
|
||||
|
||||
val abc = StringLiteralValue("abc", false, Position("", 0, 0, 0))
|
||||
val abd = StringLiteralValue("abd", false, Position("", 0, 0, 0))
|
||||
val abc = StringLiteralValue("abc", false, Position.DUMMY)
|
||||
val abd = StringLiteralValue("abd", false, Position.DUMMY)
|
||||
assertEquals(abc, abc)
|
||||
assertTrue(abc!=abd)
|
||||
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
|
||||
|
||||
|
||||
|
||||
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,31 +135,29 @@ Design principles and features
|
||||
|
||||
- It is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...)
|
||||
The generated output is a machine code program runnable on actual 8-bit 6502 hardware.
|
||||
- Usable on most operating systems.
|
||||
- Based on simple and familiar imperative structured programming paradigm.
|
||||
- 'One statement per line' code style, resulting in clear readable programs.
|
||||
- Based on simple and familiar imperative structured programming (it looks like a mix of C and Python)
|
||||
- 'One statement per line' code, resulting in clear readable programs.
|
||||
- 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,
|
||||
and inline assembly to have full control when every cycle or byte matters
|
||||
- Arbitrary number of subroutine parameters (constrained only by available memory)
|
||||
- Nested subroutines can access variables from outer scopes, this avoids the need and overhead to pass everything via parameters
|
||||
- Arbitrary number of subroutine parameters
|
||||
- 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.
|
||||
- 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
|
||||
the compiled program in an emulator and provide debugging information to the 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.
|
||||
the compiled program in an emulator and provide debugging information to this emulator.
|
||||
- Arbitrary control flow jumps and branches are possible,
|
||||
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
|
||||
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
|
||||
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``
|
||||
- Assembling the generated code into a program wil be done by an external cross-assembler tool.
|
||||
|
||||
|
||||
.. _requirements:
|
||||
|
@ -135,14 +135,18 @@ Scopes are created using either of these two statements:
|
||||
- blocks (top-level named scope)
|
||||
- subroutines (nested named scope)
|
||||
|
||||
.. note::
|
||||
Unlike many other programming languages, a new scope is *not* created inside
|
||||
for, while and repeat statements, nor for the if statement and branching conditionals.
|
||||
This can be a bit restrictive because as a programmer you have to think harder about what variables you
|
||||
want to use inside a subroutine. But it is done precisely for this reason: memory in prog8's
|
||||
.. important::
|
||||
Unlike most other programming languages, a new scope is *not* created inside
|
||||
for, while and repeat statements, the if statement, and the branching conditionals.
|
||||
These all share the same scope from the subroutine they're defined in.
|
||||
You can define variables in these blocks, but these will be treated as if they
|
||||
were defined in the subroutine instead.
|
||||
This can seem a bit restrictive because you have to think harder about what variables you
|
||||
want to use inside the subroutine, to avoid clashes.
|
||||
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 the programmer has to think about.
|
||||
variables intelligently. So for now that is something you have to think about yourself.
|
||||
|
||||
|
||||
Program Start and Entry Point
|
||||
@ -152,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,
|
||||
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)::
|
||||
|
||||
main {
|
||||
@ -181,7 +178,7 @@ Variables and values
|
||||
--------------------
|
||||
|
||||
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.
|
||||
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::
|
||||
@ -375,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
|
||||
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
|
||||
be set to zero *but only for the first run of the program*. A second run will utilize the last value
|
||||
where it left off (but your code will be a bit smaller because no initialization instructions
|
||||
are generated)
|
||||
or even multiple invocations of the entire program.
|
||||
If you omit the initial value, zero will be used instead.
|
||||
|
||||
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
|
||||
@ -387,15 +382,6 @@ If you do modify them in-place, you should take care yourself that they work as
|
||||
expected when the program is restarted.
|
||||
(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
|
||||
-----
|
||||
|
@ -216,7 +216,8 @@ Variable declarations
|
||||
|
||||
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,
|
||||
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.
|
||||
The syntax is::
|
||||
|
||||
@ -321,7 +322,7 @@ Constants
|
||||
^^^^^^^^^
|
||||
|
||||
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)::
|
||||
|
||||
const byte max_age = 99
|
||||
|
@ -2,29 +2,11 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- remove statements after an exit() or return
|
||||
- fix warnings about that unreachable code?
|
||||
- finalize (most) of the still missing "new" assignment asm code generation
|
||||
|
||||
- option to load library files from a directory instead of the embedded ones
|
||||
|
||||
|
||||
Memory Block Operations integrated in language?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
array/string memory block operations?
|
||||
|
||||
- vector inc/dec/add/sub/mul/div...? (on array or string):
|
||||
``arrayvar++ / arrayvar-- / arrayvar += 2 / arrayvar -= 2 / arrayvar *= 3 / arrayvar /= 3``
|
||||
|
||||
- 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.
|
||||
- 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://murray2.com/forums/commander-x16.9/ and https://github.com/commanderx16/x16-docs
|
||||
|
||||
|
||||
More optimizations
|
||||
@ -32,12 +14,16 @@ More optimizations
|
||||
|
||||
Add more compiler optimizations to the existing ones.
|
||||
|
||||
- on the language AST level
|
||||
- on the final assembly source level
|
||||
- more targeted optimizations for assigment asm code, such as the following:
|
||||
- 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
|
||||
- working subroutine inlining (start with trivial routines, grow to taking care of vars and identifier refs to them)
|
||||
- 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?
|
||||
- working subroutine inlining (taking care of vars and identifier refs to them)
|
||||
|
||||
Also some library routines and code patterns could perhaps be optimized further
|
||||
- more optimizations on the language AST level
|
||||
- more optimizations on the final assembly source level
|
||||
|
||||
|
||||
Eval stack redesign? (lot of work)
|
||||
@ -45,17 +31,18 @@ Eval stack redesign? (lot of work)
|
||||
|
||||
The eval stack is now a split lsb/msb stack using X as the stackpointer.
|
||||
Is it easier/faster to just use a single page unsplit stack?
|
||||
It could then even be moved into the zeropage to 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.
|
||||
|
||||
Allocate a fixed word in ZP that is the TOS so we can always operate on TOS directly
|
||||
without having to to index into the stack?
|
||||
Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly
|
||||
without having to index with X into the eval stack all the time?
|
||||
This could GREATLY improve code size and speed for operations that work on just a single value.
|
||||
|
||||
|
||||
Bugs
|
||||
^^^^
|
||||
Ofcourse there are still bugs to fix ;)
|
||||
Bug Fixing
|
||||
^^^^^^^^^^
|
||||
Ofcourse there are always bugs to fix ;)
|
||||
|
||||
|
||||
Misc
|
||||
@ -63,4 +50,3 @@ Misc
|
||||
|
||||
Several ideas were discussed on my reddit post
|
||||
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/
|
||||
|
||||
|
@ -106,16 +106,15 @@ main {
|
||||
|
||||
check_eval_stack()
|
||||
|
||||
c64scr.print("\nyou should see no errors above.")
|
||||
c64scr.print("\nyou should see no errors printed above (only at first run).")
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -79,10 +79,10 @@ main {
|
||||
|
||||
uw1 = 2222
|
||||
uw2 = 999
|
||||
if sgn(uw2 as word - uw1 as word) != -1
|
||||
c64scr.print("sgn2 error6\n")
|
||||
if sgn(uw2 - uw1) != -1
|
||||
c64scr.print("sgn2 error6\n")
|
||||
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
|
||||
|
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
|
||||
|
||||
forever {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
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 {
|
||||
forever {
|
||||
plot(y1)
|
||||
if plotx==x2
|
||||
return
|
||||
plotx++
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forever {
|
||||
plot(y1)
|
||||
if plotx==x2
|
||||
return
|
||||
plotx--
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if positive_ix {
|
||||
forever {
|
||||
plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
if d > dy {
|
||||
plotx++
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forever {
|
||||
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
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -111,11 +111,10 @@ main {
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -110,12 +110,11 @@ main {
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -110,12 +110,11 @@ main {
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -110,12 +110,11 @@ main {
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -142,12 +142,11 @@ main {
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -93,12 +93,11 @@ main {
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -109,11 +109,10 @@ main {
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,12 +93,11 @@ main {
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -123,12 +123,11 @@ main {
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -159,12 +159,11 @@ main {
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
c64scr.print("stack x=")
|
||||
c64scr.print_ub(X)
|
||||
if X==255
|
||||
c64scr.print(" ok\n")
|
||||
else
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
BIN
examples/compiled/balloonflight.prg
Normal file
BIN
examples/compiled/balloonflight.prg
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
examples/compiled/line-circle-gfx.prg
Normal file
BIN
examples/compiled/line-circle-gfx.prg
Normal file
Binary file not shown.
BIN
examples/compiled/line-circle-txt.prg
Normal file
BIN
examples/compiled/line-circle-txt.prg
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user