Compare commits

...

97 Commits
v1.80 ... v1.91

Author SHA1 Message Date
95e76058d3 version 2020-04-03 23:55:29 +02:00
a6bee6a860 some slight tweaks to asm for setting float value in array 2020-04-03 22:44:10 +02:00
d22780ee44 implemented asm for lsl array values 2020-04-03 21:45:52 +02:00
f8b0b9575d implemented asm for rol array values 2020-04-03 21:31:39 +02:00
4274fd168e implemented asm for rol2 array values 2020-04-03 21:24:55 +02:00
be7f5957f3 implemented asm for ror2 array values 2020-04-03 21:04:42 +02:00
f2e5d987a9 implemented asm for ror array values 2020-04-03 00:03:42 +02:00
f01173d8db fixed compilation of clear/set_carry() and clear/set_irqd() functions 2020-04-03 00:00:58 +02:00
15e8e0bf6d implemented asm for lsr array values 2020-04-02 23:38:45 +02:00
2c59cbdece fixed a crash in astchecking of array init values 2020-04-02 18:40:04 +02:00
b73da4ed02 some more obvious optimizations for X+X and X-X 2020-03-31 23:54:01 +02:00
267adb4612 doc 2020-03-29 03:06:51 +02:00
05c73fa8bc version 2020-03-28 17:06:59 +01:00
bfe9f442e6 balloon 2020-03-28 17:06:17 +01:00
0deadb694b updated the compiled examples 2020-03-28 14:31:31 +01:00
bed34378be doc 2020-03-28 14:24:00 +01:00
5927cf2d43 added turtle graphics example 2020-03-28 14:17:35 +01:00
fffe36e358 fix bresenham line 2020-03-28 13:42:24 +01:00
fac2a2d7cb fast asm plot 2020-03-28 00:36:44 +01:00
0af5582ca7 fix compiler crash for undefined symbol in expression 2020-03-27 23:09:46 +01:00
582d31263c better lines and circles 2020-03-27 00:09:17 +01:00
4108a528e1 proepr compiler error when there's no main module 2020-03-26 23:22:01 +01:00
ab7d7c2907 fix comparison of memory expressions (this error prevented some optimizations) 2020-03-26 22:59:42 +01:00
152888ee93 fix direcetmemoryread invalid asm 2020-03-26 22:46:05 +01:00
22f8f4f359 fixed memory pointer access asm code for direct reads and direct assignments 2020-03-26 19:20:39 +01:00
5f3a9e189a doc 2020-03-26 01:20:04 +01:00
b734dc44fd fix invalid assembly for @(address)++/-- 2020-03-26 01:13:20 +01:00
fab224f509 fix compiler crashing with invalid array initializer built from single integer 2020-03-25 01:23:54 +01:00
2f05ebb966 bitmap lines and circles 2020-03-25 01:07:42 +01:00
a335ba519a fix warnings about unreachable code 2020-03-24 22:37:42 +01:00
8805693ed2 cleaned up the way return statements are added to avoid code falling through in/out of subroutines 2020-03-24 22:02:50 +01:00
f2bb238e9b cleaned up various ast checks/mutations 2020-03-24 19:37:54 +01:00
131fe670a4 optimized scroll routines by removing needless twin loops 2020-03-24 17:33:47 +01:00
11e9539416 smooth scroll 2020-03-24 02:42:32 +01:00
3881ebe429 begun skramble clone 2020-03-24 01:47:02 +01:00
29d1b8802e whitespace 2020-03-24 00:24:51 +01:00
bcc75732e9 optimize asm jsr+rts into jmp 2020-03-23 23:51:27 +01:00
50a85ee6b0 attempt to optimize asm for bitshifts more. 2020-03-23 22:59:29 +01:00
2c7424fd43 fix: datatype mismatch in optimized bitshift const value 2020-03-23 22:35:23 +01:00
7426587c38 fix: add proper return statement type cast if needed, now also for non constant values 2020-03-23 19:49:11 +01:00
1f39749a5e tweak bitshift asm 2020-03-23 17:35:58 +01:00
ca63051c71 replaced todo's that aren't real todo's with regular exception 2020-03-23 13:00:44 +01:00
6dd44aaf0d compiler main cleanup 2020-03-23 02:54:04 +01:00
f89457ba68 fixed var initialization bug in anonymous scopes 2020-03-23 02:09:30 +01:00
efef205fcf doc 2020-03-23 01:24:54 +01:00
0c561d8528 fixed subroutine parameter value issue 2020-03-23 00:13:46 +01:00
8bfa2c4c02 proper initialization of block-level global variables 2020-03-22 22:47:05 +01:00
f0d4c3aba9 moved initialvalues to asmgen, fixed sgn bug and internal float 0.0 variable disappearing bug 2020-03-22 18:17:12 +01:00
3a99115070 Initial variable values semantics changed: now always sets value at program (re)start (except strings/arrays).
This may change later by introducing a compiler option to choose a strategy, perhaps.
2020-03-22 15:12:26 +01:00
7232134931 fix some compiler errors 2020-03-22 13:47:13 +01:00
954e911eb3 optimized zeros array initializer 2020-03-22 02:58:51 +01:00
63c073c93f got rid of the Simulator / AST VM 2020-03-22 02:50:34 +01:00
78feef9d59 simplified handling of initial vardecl values in codegeneration 2020-03-22 02:45:42 +01:00
4fbdd6d570 fix ubyte number print bug for 100-109 and 200-209 missing the tens digit 2020-03-22 01:49:05 +01:00
4929c198ba tweak error reporting, expanded lines and circles example 2020-03-22 00:43:46 +01:00
9409f17372 bugfixes in new optimization routines 2020-03-21 23:09:18 +01:00
43781c02d0 tweaked ast modifications 2020-03-21 18:42:40 +01:00
824f06e17f new var init values 2020-03-21 14:54:19 +01:00
21dbc6da97 doc 2020-03-21 12:51:32 +01:00
270ea54ff7 now properly compile assignment of struct literal value to struct variable (outside of vardecl) 2020-03-21 00:57:20 +01:00
771ac7aba7 error when struct literal value element count doesn't match struct members in assignment 2020-03-20 23:14:03 +01:00
97d36243f2 don't include the generated parser java files in git 2020-03-20 22:53:56 +01:00
511b47bac4 fix compiler crash when initializing struct var with something other than a struct literal 2020-03-20 22:48:33 +01:00
f265199fbe replaced typecastsAdder with version based on astwalker 2020-03-20 22:28:18 +01:00
a191ec71a4 this is not modifying the ast 2020-03-19 23:16:58 +01:00
82dce2dd53 added Foreverloop statement to the ast simulator 2020-03-19 22:45:27 +01:00
29ac160811 applying new astwalker for modifications 2020-03-19 22:40:49 +01:00
5e50ea14f8 applying new astwalker for modifications 2020-03-19 21:30:01 +01:00
40e6091506 new astvisitor tryout 2020-03-19 00:01:57 +01:00
0ee4d420b1 slight tweaks on the Ast, Program (the top level) is now a Node as well 2020-03-18 22:29:30 +01:00
66acce9e8e doc 2020-03-15 01:49:16 +01:00
6c23ae14ab ver 2020-03-15 01:37:01 +01:00
6f000d0d26 fix datatype warning 2020-03-15 01:14:44 +01:00
9d7eb3be5a fix error reporting of constantfolding, and number of errors printed 2020-03-15 01:10:08 +01:00
835555171e fix function call arg type mismatch crash 2020-03-15 00:50:59 +01:00
68ce4a1bf0 labels are now prefixed with underscore in assembly to fix undefined symbol errors from the assembler 2020-03-15 00:23:54 +01:00
a995867deb added check for duplicate label definitions 2020-03-15 00:16:50 +01:00
6bd99d63b4 cleanup of error reporting 2020-03-14 23:47:26 +01:00
baf5d3041a cleanup of error reporting 2020-03-14 23:15:44 +01:00
a326ffa00a added warning about sgn() of unsigned type 2020-03-14 21:09:34 +01:00
d28dd92b47 refreshed examples 2020-03-14 18:11:38 +01:00
1de328b2e8 added forever-loop and optimizer 2020-03-14 18:11:04 +01:00
51bb902162 added bresenham and circle example 2020-03-14 17:11:10 +01:00
4fd14f1366 doc updates 2020-03-14 15:20:04 +01:00
91d9559f79 avoid pulling in the dbus libraries for now 2020-03-14 14:40:39 +01:00
3245a9b157 restricted block to only directive/subroutine/vardecl/inlineasm 2020-03-14 14:20:55 +01:00
2b28493bba simplified module grammar rules 2020-03-14 13:44:13 +01:00
1382728bd2 warning about unreachable code after a return statement
added some dbus experiments for future compilation service
2020-03-14 13:12:01 +01:00
0422ad080a added exit function to astvm simulator 2020-03-13 02:44:01 +01:00
64d682bfde todo 2020-03-13 02:33:02 +01:00
b182f7e693 optimizer removes unreachable code following call to exit() 2020-03-13 02:31:53 +01:00
e6be428589 compiler warning for unreachable code following a call to exit() 2020-03-13 02:21:37 +01:00
85c7f8314b added exit(rc) builtin function to immediately exit the program with a return code in A register 2020-03-13 02:08:18 +01:00
796d07a7f8 fix crash in asm code generated for bitshift operation with memory address operand 2020-03-13 01:26:53 +01:00
2af86a10b2 remove stack error comments 2020-03-13 00:52:52 +01:00
7fbe486dff fix eval stack register X error in print_uw 2020-03-13 00:50:30 +01:00
87e5a9859a remove autogenerated labels from vice mon list, fixes #17 2020-03-12 22:33:58 +01:00
145 changed files with 6573 additions and 12319 deletions

2
.gitignore vendored
View File

@ -14,7 +14,7 @@ docs/build
out/
parser/**/*.interp
parser/**/*.tokens
parser/**/*.java
*.py[cod]
*.egg
*.egg-info

View File

@ -1,5 +1,6 @@
[![saythanks](https://img.shields.io/badge/say-thanks-ff69b4.svg)](https://saythanks.io/to/irmen)
[![Build Status](https://travis-ci.org/irmen/prog8.svg?branch=master)](https://travis-ci.org/irmen/prog8)
[![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](https://prog8.readthedocs.io/)
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
===========================================================================
@ -35,15 +36,13 @@ Rapid edit-compile-run-debug cycle:
- option to automatically run the program in the Vice emulator
- 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.
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
--------------

View File

@ -34,6 +34,7 @@ dependencies {
implementation 'org.antlr:antlr4-runtime:4.8'
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
// implementation 'net.razorvine:ksim65:1.6'
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
implementation project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
@ -100,7 +101,7 @@ test {
// Show test results.
testLogging {
events "passed", "skipped", "failed"
events "skipped", "failed"
}
}

Binary file not shown.

View File

@ -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

View File

@ -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

View File

@ -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
@ -975,6 +893,7 @@ asmsub print_uw (uword value @ AY) clobbers(A,Y) {
%asm {{
stx c64.SCRATCH_ZPREGX
jsr c64utils.uword2decimal
ldx c64.SCRATCH_ZPREGX
ldy #0
- lda c64utils.uword2decimal.decTenThousands,y
beq _allzero

View File

@ -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

View File

@ -651,6 +651,18 @@ greatereq_w .proc
bmi equal_b._equal_b_false
.pend
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
func_exit .proc
; -- immediately exit the program with a return code in the A register
lda c64.ESTACK_LO+1,x
ldx orig_stackpointer
txs
rts ; return to original caller
.pend
func_read_flags .proc
; -- put the processor status register on the stack
php
@ -1776,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
@ -1791,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

View File

@ -1 +1 @@
1.80
1.91

View File

@ -2,13 +2,13 @@ package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.compiler.*
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.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
@ -39,7 +39,6 @@ private fun compileMain(args: Array<String>) {
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
val 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)
@ -118,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.")

View File

@ -283,14 +283,6 @@ 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)
output(" ${assignment.aug_op} ")
@ -331,6 +323,11 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
whileLoop.body.accept(this)
}
override fun visit(foreverLoop: ForeverLoop) {
output("forever ")
foreverLoop.body.accept(this)
}
override fun visit(repeatLoop: RepeatLoop) {
output("repeat ")
repeatLoop.body.accept(this)

View File

@ -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 {
@ -158,6 +163,31 @@ interface INameScope {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
}
fun getAllLabels(label: String): List<Label> {
val result = mutableListOf<Label>()
fun find(scope: INameScope) {
scope.statements.forEach {
when(it) {
is Label -> result.add(it)
is INameScope -> find(it)
is IfStatement -> {
find(it.truepart)
find(it.elsepart)
}
is RepeatLoop -> find(it.body)
is ForeverLoop -> find(it.body)
is WhileLoop -> find(it.body)
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
else -> { /* do nothing */ }
}
}
}
find(this)
return result
}
}
interface IAssignable {
@ -167,7 +197,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
@ -187,6 +217,23 @@ class Program(val name: String, val modules: MutableList<Module>) {
}
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
}
}
class Module(override val name: String,
@ -194,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>()
@ -207,10 +255,20 @@ 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
}
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)
@ -221,6 +279,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
@ -246,7 +308,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 NameError("wrong identifier target: $stmt", stmt.position)
else -> throw SyntaxError("wrong identifier target for $scopedName: $stmt", stmt.position)
}
}
}

View File

@ -19,13 +19,13 @@ import java.nio.file.Path
private data class NumericLiteral(val number: Number, val datatype: DataType)
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source)
val directives = this.directive().map { it.toAst() }
val blocks = this.block().map { it.toAst(isLibrary) }
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), isLibrary, source)
}
private fun ParserRuleContext.toPosition() : Position {
val customTokensource = this.start.tokenSource as? CustomLexer
val filename =
@ -38,27 +38,23 @@ private fun ParserRuleContext.toPosition() : Position {
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
}
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : Statement {
val directive = directive()?.toAst()
if(directive!=null) return directive
val block = block()?.toAst(isInLibrary)
if(block!=null) return block
throw FatalAstException(text)
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement {
val blockstatements = block_statement().map {
when {
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
it.directive()!=null -> it.directive().toAst()
it.inlineasm()!=null -> it.inlineasm().toAst()
else -> throw FatalAstException("weird block statement $it")
}
}
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
}
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement =
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList()
private fun prog8Parser.StatementContext.toAst() : Statement {
private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
vardecl()?.let { return it.toAst() }
varinitializer()?.let {
@ -66,7 +62,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.VAR,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@ -114,7 +110,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.CONST,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@ -131,7 +127,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.MEMORY,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@ -142,6 +138,28 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
)
}
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
toPosition())
}
throw FatalAstException("weird variable decl $this")
}
private fun prog8Parser.SubroutinedeclarationContext.toAst() : Subroutine {
return when {
subroutine()!=null -> subroutine().toAst()
asmsubroutine()!=null -> asmsubroutine().toAst()
romsubroutine()!=null -> romsubroutine().toAst()
else -> throw FatalAstException("weird subroutine decl $this")
}
}
private fun prog8Parser.StatementContext.toAst() : Statement {
val vardecl = variabledeclaration()?.toAst()
if(vardecl!=null) return vardecl
assignment()?.let {
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
}
@ -175,8 +193,8 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val returnstmt = returnstmt()?.toAst()
if(returnstmt!=null) return returnstmt
val sub = subroutine()?.toAst()
if(sub!=null) return sub
val subroutine = subroutinedeclaration()?.toAst()
if(subroutine!=null) return subroutine
val asm = inlineasm()?.toAst()
if(asm!=null) return asm
@ -193,31 +211,22 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val whileloop = whileloop()?.toAst()
if(whileloop!=null) return whileloop
val foreverloop = foreverloop()?.toAst()
if(foreverloop!=null) return foreverloop
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
val asmsubstmt = asmsubroutine()?.toAst()
if(asmsubstmt!=null) return asmsubstmt
val romsubstmt = romsubroutine()?.toAst()
if(romsubstmt!=null) return romsubstmt
val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
toPosition())
}
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
@ -225,7 +234,7 @@ private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
subdecl.asmClobbers, null, true, statements, toPosition())
}
private fun prog8Parser.RomsubroutineContext.toAst(): Statement {
private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val address = integerliteral().toAst().number.toInt()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
@ -233,7 +242,6 @@ private fun prog8Parser.RomsubroutineContext.toAst(): Statement {
subdecl.asmClobbers, address, true, mutableListOf(), toPosition())
}
private class AsmsubDecl(val name: String,
val parameters: List<SubroutineParameter>,
val returntypes: List<DataType>,
@ -241,7 +249,6 @@ private class AsmsubDecl(val name: String,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<Register>)
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
val name = identifier().text
val params = asmsub_params()?.toAst() ?: emptyList()
@ -254,7 +261,6 @@ private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
}
private class AsmSubroutineParameter(name: String,
type: DataType,
val registerOrPair: RegisterOrPair?,
@ -271,7 +277,6 @@ private class AsmSubroutineReturn(val type: DataType,
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
= this.register().asSequence().map { it.toAst() }.toSet()
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
@ -285,10 +290,8 @@ private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParamete
!it.stack?.text.isNullOrEmpty(), toPosition())
}
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
val void = this.VOID() != null
val location = scoped_identifier().toAst()
@ -298,7 +301,6 @@ private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
}
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
@ -307,11 +309,9 @@ private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun prog8Parser.InlineasmContext.toAst() =
InlineAssembly(INLINEASMBLOCK().text, toPosition())
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
return Return(expression()?.toAst(), toPosition())
}
@ -322,11 +322,9 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
return Jump(address, identifier, null, toPosition())
}
private fun prog8Parser.LabeldefContext.toAst(): Statement =
Label(children[0].text, toPosition())
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
@ -345,14 +343,12 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
return returns.datatype().map { it.toAst() }
}
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
SubroutineParameter(it.varname.text, datatype, it.toPosition())
}
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
val register = register()?.toAst()
val identifier = scoped_identifier()
@ -371,15 +367,12 @@ private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperC
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
private fun prog8Parser.DirectiveContext.toAst() : Directive =
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
val str = stringliteral()
if(str?.ALT_STRING_ENCODING() != null)
@ -388,7 +381,6 @@ private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
}
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
val integer: Int
@ -440,7 +432,6 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
}
}
private fun prog8Parser.ExpressionContext.toAst() : Expression {
val litval = literalvalue()
@ -526,39 +517,31 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
throw FatalAstException(text)
}
private fun prog8Parser.StringliteralContext.toAst(): StringLiteralValue =
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
return ArrayIndexedExpression(scoped_identifier().toAst(),
arrayindex().toAst(),
toPosition())
}
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
IdentifierReference(listOf(text), toPosition())
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
IdentifierReference(NAME().map { it.text }, toPosition())
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
"true" -> true
"false" -> false
else -> throw FatalAstException(text)
}
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
expression().map { it.toAst() }.toTypedArray()
@ -576,7 +559,6 @@ private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
}
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@ -589,7 +571,6 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst()
val loopvar = identifier()?.toAst()
@ -602,12 +583,10 @@ private fun prog8Parser.ForloopContext.toAst(): ForLoop {
return ForLoop(loopregister, loopvar, iterable, scope, toPosition())
}
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
val condition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@ -616,6 +595,12 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
return WhileLoop(condition, scope, toPosition())
}
private fun prog8Parser.ForeverloopContext.toAst(): ForeverLoop {
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return ForeverLoop(scope, toPosition())
}
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
val untilCondition = expression().toAst()
@ -681,4 +666,3 @@ internal fun unescape(str: String, position: Position): String {
}
return result.joinToString("")
}

View File

@ -150,8 +150,13 @@ 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) {}
}
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)
}
}

View File

@ -3,35 +3,42 @@ package prog8.ast.base
import prog8.parser.ParsingFailedError
fun printErrors(errors: List<Any>, moduleName: String) {
val reportedMessages = mutableSetOf<String>()
System.err.print("\u001b[91m") // bright red
errors.forEach {
val msg = it.toString()
if(msg !in reportedMessages) {
System.err.println(msg)
reportedMessages.add(msg)
}
class ErrorReporter {
private enum class MessageSeverity {
WARNING,
ERROR
}
System.err.print("\u001b[0m") // reset color
if(reportedMessages.isNotEmpty())
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
}
fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
print("\u001b[93m") // bright yellow
print("$position Warning: $msg")
if(detailInfo==null)
print("\n")
else
println(": $detailInfo\n")
print("\u001b[0m") // normal
}
fun printWarning(msg: String) {
print("\u001b[93m") // bright yellow
print("Warning: $msg")
print("\u001b[0m\n") // normal
private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position)
private val messages = mutableListOf<CompilerMessage>()
private val alreadyReportedMessages = mutableSetOf<String>()
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
fun handle() {
var numErrors = 0
var numWarnings = 0
messages.forEach {
when(it.severity) {
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
}
val msg = "${it.position} ${it.severity} ${it.message}".trim()
if(msg !in alreadyReportedMessages) {
System.err.println(msg)
alreadyReportedMessages.add(msg)
when(it.severity) {
MessageSeverity.WARNING -> numWarnings++
MessageSeverity.ERROR -> numErrors++
}
}
System.err.print("\u001b[0m") // reset color
}
messages.clear()
if(numErrors>0)
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
}
fun isEmpty() = messages.isEmpty()
}

View File

@ -6,17 +6,13 @@ class FatalAstException (override var message: String) : Exception(message)
open class AstException (override var message: String) : Exception(message)
class SyntaxError(override var message: String, val position: Position) : AstException(message) {
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Syntax error: $message"
}
open class NameError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Name error: $message"
}
class ExpressionError(message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Error: $message"
}
class UndefinedSymbolError(symbol: IdentifierReference)
: NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)

View File

@ -4,66 +4,64 @@ import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.compiler.CompilationOptions
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
import prog8.compiler.target.AsmVariableAndReturnsPreparer
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
// 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)
}
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(this, compilerOptions)
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
val checker = AstChecker(this, compilerOptions, errors)
checker.visit(this)
printErrors(checker.result(), name)
}
internal fun Program.anonscopeVarsCleanup() {
val mover = AnonymousScopeVarsCleanup(this)
mover.visit(this)
printErrors(mover.result(), name)
internal fun Program.prepareAsmVariablesAndReturns(errors: ErrorReporter) {
val fixer = AsmVariableAndReturnsPreparer(this, errors)
fixer.visit(this)
fixer.applyModifications()
}
internal fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(this)
val initvalueCreator = AddressOfInserter(this)
initvalueCreator.visit(this)
initvalueCreator.applyModifications()
val checker = StatementReorderer(this)
checker.visit(this)
}
internal fun Program.addTypecasts() {
val caster = TypecastsAdder(this)
internal fun Program.addTypecasts(errors: ErrorReporter) {
val caster = TypecastsAdder(this, errors)
caster.visit(this)
caster.applyModifications()
}
internal fun Module.checkImportedValid() {
val checker = ImportedModuleDirectiveRemover()
checker.visit(this)
printErrors(checker.result(), name)
val imr = ImportedModuleDirectiveRemover()
imr.visit(this, this.parent)
imr.applyModifications()
}
internal fun Program.checkRecursion() {
val checker = AstRecursionChecker(namespace)
internal fun Program.checkRecursion(errors: ErrorReporter) {
val checker = AstRecursionChecker(namespace, errors)
checker.visit(this)
printErrors(checker.result(), name)
checker.processMessages(name)
}
internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(this)
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")
}
printErrors(checker.result(), name)
}
internal fun Program.makeForeverLoops() {
val checker = ForeverLoopsMaker()
checker.visit(this)
checker.applyModifications()
}
internal fun Program.removeNopsFlattenAnonScopes() {
val flattener = FlattenAnonymousScopesAndNopRemover()
flattener.visit(this)
}

View File

@ -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.*
@ -10,7 +11,7 @@ import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import java.util.Objects
import java.util.*
import kotlin.math.abs
@ -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,16 @@ 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
}
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 +105,15 @@ 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")
}
}
override fun toString(): String {
return "[$left $operator $right]"
}
@ -105,6 +123,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 +225,19 @@ 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")
}
}
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 +265,15 @@ 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
}
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 +296,17 @@ 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
}
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 +317,15 @@ 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
}
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 +375,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 +468,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 +497,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 +530,19 @@ 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
}
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 +621,21 @@ 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")
}
}
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 +707,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 +740,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 +762,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 +800,15 @@ 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
}
}
override fun constValue(program: Program) = constValue(program, true)
private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteralValue? {
@ -737,6 +847,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 {

View File

@ -1,7 +1,7 @@
package prog8.ast.expressions
import java.util.Objects
import prog8.ast.base.DataType
import java.util.*
object InferredTypes {

View File

@ -0,0 +1,93 @@
package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.IterableDatatypes
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Statement
import prog8.ast.statements.Subroutine
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.functions.FSignature
internal class AddressOfInserter(val program: Program): AstWalker() {
// Insert AddressOf (&) expression where required (string params to a UWORD function param etc).
// TODO join this into the StatementReorderer?
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
var parentStatement: Node = functionCall
while(parentStatement !is Statement)
parentStatement = parentStatement.parent
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
return addAddressOfExprIfNeeded(targetStatement, functionCall.args, functionCall)
} else {
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.args, functionCall)
}
return emptyList()
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
return addAddressOfExprIfNeeded(targetStatement, functionCallStatement.args, functionCallStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.args, functionCallStatement)
}
return emptyList()
}
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
val replacements = mutableListOf<IAstModification>()
for(argparam in subroutine.parameters.withIndex().zip(args)) {
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
if(idref!=null) {
val variable = idref.targetVarDecl(program.namespace)
if(variable!=null && variable.datatype in IterableDatatypes) {
replacements += IAstModification.ReplaceNode(
args[argparam.first.index],
AddressOf(idref, idref.position),
parent as Node)
}
}
}
}
return replacements
}
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FSignature, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
val replacements = mutableListOf<IAstModification>()
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")
replacements += IAstModification.ReplaceNode(
args[arg.first.index],
AddressOf(argvalue, argvalue.position),
parent as Node)
}
}
return replacements
}
}

View File

@ -1,47 +0,0 @@
package prog8.ast.processing
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.NameError
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Statement
import prog8.ast.statements.VarDecl
class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
fun result(): List<AstException> {
return checkResult
}
override fun visit(program: Program) {
varsToMove.clear()
super.visit(program)
for((scope, decls) in varsToMove) {
val sub = scope.definingSubroutine()!!
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing!=null) {
checkResult.add(NameError("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position))
conflicts = true
}
}
if (!conflicts) {
decls.forEach { scope.remove(it) }
sub.statements.addAll(0, decls)
decls.forEach { it.parent = sub }
}
}
}
override fun visit(scope: AnonymousScope): Statement {
val scope2 = super.visit(scope) as AnonymousScope
val vardecls = scope2.statements.filterIsInstance<VarDecl>()
varsToMove[scope2] = vardecls
return scope2
}
}

File diff suppressed because it is too large Load Diff

View File

@ -11,19 +11,13 @@ import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
internal class AstIdentifiersChecker(private val program: Program,
private val errors: ErrorReporter) : IAstModifyingVisitor {
private var blocks = mutableMapOf<String, Block>()
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
internal fun result(): List<AstException> {
return checkResult
}
private fun nameError(name: String, position: Position, existing: Statement) {
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
}
override fun visit(module: Module) {
@ -59,15 +53,15 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(decl: VarDecl): Statement {
// first, check if there are datatype errors on the vardecl
decl.datatypeErrors.forEach { checkResult.add(it) }
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
// now check the identifier
if(decl.name in BuiltinFunctions)
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in CompilationTarget.machine.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position))
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
@ -76,7 +70,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
return super.visit(decl) // don't do this multiple times
if(decl.struct==null) {
checkResult.add(NameError("undefined struct type", decl.position))
errors.err("undefined struct type", decl.position)
return super.visit(decl)
}
@ -84,7 +78,12 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
if(decl.value is NumericLiteralValue) {
checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position))
errors.err("you cannot initialize a struct using a single value", decl.position)
return super.visit(decl)
}
if(decl.value != null && decl.value !is StructLiteralValue) {
errors.err("initializing requires struct literal value", decl.value?.position ?: decl.position)
return super.visit(decl)
}
@ -104,10 +103,10 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(subroutine: Subroutine): Statement {
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position))
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
errors.err("builtin function cannot be redefined", subroutine.position)
} else {
// already reported elsewhere:
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
@ -148,8 +147,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
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)
}
@ -157,7 +155,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position))
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
}
}
return super.visit(subroutine)
@ -165,15 +163,21 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(label: Label): Statement {
if(label.name in CompilationTarget.machine.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${label.name}'", label.position))
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position))
errors.err("builtin function cannot be redefined", label.position)
} else {
val existing = program.namespace.lookup(listOf(label.name), label)
if (existing != null && existing !== label)
nameError(label.name, label.position, existing)
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
for(el in existing) {
if(el === label || el.name != label.name)
continue
else {
nameError(label.name, label.position, el)
break
}
}
}
return super.visit(label)
}
@ -185,7 +189,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
// additional interation count variable in their scope.
if(forLoop.loopRegister!=null) {
if(forLoop.loopRegister == Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
} else {
val loopVar = forLoop.loopVar
if (loopVar != null) {
@ -209,23 +213,10 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(assignTarget: AssignTarget): AssignTarget {
if(assignTarget.register== Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
return super.visit(assignTarget)
}
override fun visit(returnStmt: Return): Statement {
if(returnStmt.value!=null) {
// possibly adjust any literal values returned, into the desired returning data type
val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size!=1)
return returnStmt // mismatch in number of return values, error will be printed later.
val lval = returnStmt.value as? NumericLiteralValue
returnStmt.value = lval?.cast(subroutine.returntypes.single()) ?: returnStmt.value!!
}
return super.visit(returnStmt)
}
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
@ -264,7 +255,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
val vardecl = string.parent as? VarDecl
// intern the string; move it into the heap
if (string.value.length !in 1..255)
checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position))
errors.err("string literal length must be between 1 and 255", string.position)
return if (vardecl != null)
string
else
@ -303,7 +294,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl!=null && decl.datatype !in NumericDatatypes)
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
errors.err("structs can only contain numerical types", decl.position)
}
return super.visit(structDecl)

View File

@ -1,21 +1,23 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.base.AstException
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisitor {
internal class AstRecursionChecker(private val namespace: INameScope,
private val errors: ErrorReporter) : IAstVisitor {
private val callGraph = DirectedGraph<INameScope>()
internal fun result(): List<AstException> {
fun processMessages(modulename: String) {
val cycle = callGraph.checkForCycle()
if(cycle.isEmpty())
return emptyList()
return
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain"))
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
@ -44,7 +46,6 @@ internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisi
super.visit(functionCall)
}
private class DirectedGraph<VT> {
private val graph = mutableMapOf<VT, MutableSet<VT>>()
private var uniqueVertices = mutableSetOf<VT>()

View File

@ -0,0 +1,443 @@
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 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.parent = 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)
}
}

View 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()
}
}

View File

@ -6,9 +6,10 @@ import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
interface IAstModifyingVisitor {
fun visit(program: Program) {
program.modules.forEach { visit(it) }
program.modules.forEach { it.accept(this) }
}
fun visit(module: Module) {
@ -158,6 +159,11 @@ interface IAstModifyingVisitor {
return whileLoop
}
fun visit(foreverLoop: ForeverLoop): Statement {
foreverLoop.body = foreverLoop.body.accept(this) as AnonymousScope
return foreverLoop
}
fun visit(repeatLoop: RepeatLoop): Statement {
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope

View File

@ -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) {
@ -112,6 +112,10 @@ interface IAstVisitor {
whileLoop.body.accept(this)
}
fun visit(foreverLoop: ForeverLoop) {
foreverLoop.body.accept(this)
}
fun visit(repeatLoop: RepeatLoop) {
repeatLoop.untilCondition.accept(this)
repeatLoop.body.accept(this)

View File

@ -1,36 +1,20 @@
package prog8.ast.processing
import prog8.ast.Module
import prog8.ast.base.SyntaxError
import prog8.ast.base.printWarning
import prog8.ast.Node
import prog8.ast.statements.Directive
import prog8.ast.statements.Statement
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
private val checkResult: MutableList<SyntaxError> = mutableListOf()
internal fun result(): List<SyntaxError> {
return checkResult
}
internal class ImportedModuleDirectiveRemover: AstWalker() {
/**
* Most global directives don't apply for imported modules, so remove them
*/
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) {
printWarning("ignoring module directive because it was imported", stmt.position, stmt.directive)
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()
}
}

View 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)
//
//}

View File

@ -3,50 +3,12 @@ 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.NumericDatatypes
import prog8.ast.base.VarDeclType
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 {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
@ -59,13 +21,14 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
// - all other subroutines will be moved to the end of their block.
// - sorts the choices in when statement.
// - a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
private val addReturns = mutableListOf<Pair<INameScope, Int>>()
private val addVardecls = mutableMapOf<INameScope, MutableList<VarDecl>>()
override fun visit(module: Module) {
addReturns.clear()
addVardecls.clear()
super.visit(module)
val (blocks, other) = module.statements.partition { it is Block }
@ -94,11 +57,9 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
module.statements.removeAll(directives)
module.statements.addAll(0, directives)
for(pos in addReturns) {
println(pos)
val returnStmt = Return(null, pos.first.position)
returnStmt.linkParents(pos.first as Node)
pos.first.statements.add(pos.second, returnStmt)
for((where, decls) in addVardecls) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
}
@ -123,23 +84,6 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
}
// make sure there is a 'return' in front of the first subroutine
// (if it isn't the first statement in the block itself, and isn't the program's entrypoint)
if(numSubroutinesAtEnd>0 && block.statements.size > (numSubroutinesAtEnd+1)) {
val firstSub = block.statements[block.statements.size - numSubroutinesAtEnd] as Subroutine
if(firstSub.name != "start" && block.name != "main") {
val stmtBeforeFirstSub = block.statements[block.statements.size - numSubroutinesAtEnd - 1]
if (stmtBeforeFirstSub !is Return
&& stmtBeforeFirstSub !is Jump
&& stmtBeforeFirstSub !is Subroutine
&& stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) {
val ret = Return(null, stmtBeforeFirstSub.position)
ret.linkParents(block)
block.statements.add(block.statements.size - numSubroutinesAtEnd, ret)
}
}
}
val varDecls = block.statements.filterIsInstance<VarDecl>()
block.statements.removeAll(varDecls)
block.statements.addAll(0, varDecls)
@ -148,40 +92,12 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
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)
@ -189,19 +105,38 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
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
}
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
if(scope !in addVardecls)
addVardecls[scope] = mutableListOf()
val declList = addVardecls.getValue(scope)
val existing = declList.singleOrNull { it.name==variable.name }
return if(existing!=null) {
existing
} else {
declList.add(variable)
variable
}
}
override fun visit(decl: VarDecl): Statement {
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
val target = AssignTarget(null, IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, null, declValue, decl.position)
assign.linkParents(decl.parent)
decl.value = null
addVarDecl(decl.definingScope(), decl)
return assign
}
}
return subroutine
return super.visit(decl)
}
override fun visit(assignment: Assignment): Statement {
@ -219,10 +154,11 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
// 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'
val assignments = if (assg.value is StructLiteralValue) {
flattenStructAssignmentFromStructLiteral(assg, program) // 'structvar = { ..... } '
} else {
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
@ -255,4 +191,64 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return assg
}
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
}
}
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")
}
}
}

View File

@ -2,81 +2,69 @@ 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.printWarning
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
internal class TypecastsAdder(private val program: Program): 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)
}
// if they're not assignable, we'll get a proper error later from the AstChecker
return listOf(IAstModification.ReplaceNode(
assignment.value,
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment))
}
}
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)
@ -85,14 +73,15 @@ internal class TypecastsAdder(private val program: Program): IAstModifyingVisito
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))
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
}
emptyList()
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
@ -106,93 +95,116 @@ internal class TypecastsAdder(private val program: Program): IAstModifyingVisito
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)) {
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
}
return super.visit(typecast)
return 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()
}
}

View File

@ -1,156 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature
internal class VarInitValueAndAddressOfCreator(private val program: Program): IAstModifyingVisitor {
// For VarDecls that declare an initialization value:
// Replace the vardecl with an assignment (to set the initial value),
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
// This makes sure the variables get reset to the intended value on a next run of the program.
// Variable decls without a value don't get this treatment, which means they retain the last
// value they had when restarting the program.
// This is done in a separate step because it interferes with the namespace lookup of symbols
// in other ast processors.
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
override fun visit(module: Module) {
vardeclsToAdd.clear()
super.visit(module)
// add any new vardecls to the various scopes
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
}
override fun visit(decl: VarDecl): Statement {
super.visit(decl)
if(decl.isArray && decl.value==null) {
// array datatype without initialization value, add list of zeros
val arraysize = decl.arraysize!!.size()!!
val array = ArrayLiteralValue(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: FunctionSignature, args: MutableList<Expression>, parent: Statement) {
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for(arg in args.withIndex().zip(signature.parameters)) {
val argvalue = arg.first.value
val argDt = argvalue.inferType(program)
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argvalue !is IdentifierReference)
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
val addrOf = AddressOf(argvalue, argvalue.position)
args[arg.first.index] = addrOf
addrOf.linkParents(parent)
}
}
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
if(declList.all{it.name!=variable.name})
declList.add(variable)
}
}

View File

@ -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,15 @@ 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) {}
override val expensiveToInline = false
}
@ -62,8 +68,15 @@ 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
}
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 +94,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 +106,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 +117,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 +136,14 @@ 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
}
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 +157,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 +168,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 +182,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 +196,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 +234,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 +271,20 @@ class VarDecl(val type: VarDeclType,
}
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===value)
value = replacement
}
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 +308,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 +321,11 @@ 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
}
companion object {
fun forArray(v: ArrayLiteralValue): ArrayIndex {
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
@ -295,10 +335,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)")
@ -318,19 +356,23 @@ 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")
}
}
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)
data class AssignTarget(val register: Register?,
var identifier: IdentifierReference?,
var arrayindexed: ArrayIndexedExpression?,
@ -345,8 +387,17 @@ 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")
}
}
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 +432,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 +500,14 @@ 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
}
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 +526,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 +550,18 @@ 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
}
}
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 +576,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 +603,15 @@ 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
}
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 +622,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,8 +666,15 @@ 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
}
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)"
@ -603,6 +696,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 +717,19 @@ 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")
}
}
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 +746,18 @@ 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")
}
}
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 +775,18 @@ 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")
}
}
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 +811,36 @@ 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")
}
}
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() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
body.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===body)
body = replacement
}
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,
@ -699,8 +855,17 @@ 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")
}
}
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,
@ -715,6 +880,15 @@ 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
}
}
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>>()
@ -734,6 +908,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
@ -747,12 +922,18 @@ 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
}
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)
}
@ -768,11 +949,18 @@ 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
}
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
}
@ -785,10 +973,16 @@ 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
}
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)
}

View File

@ -8,9 +8,8 @@ import prog8.compiler.target.CompilationTarget
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule
import prog8.parser.importModule
import prog8.parser.moduleName
import java.nio.file.Path
import kotlin.system.measureTimeMillis
@ -26,89 +25,29 @@ 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
println("Parsing...")
programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath)
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) {
importLibraryModule(programAst, "c64lib")
importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importLibraryModule(programAst, "math")
importLibraryModule(programAst, "prog8lib")
// perform initial syntax checks and constant folding
println("Syntax check...")
val time1 = measureTimeMillis {
programAst.checkIdentifiers()
}
//println(" time1: $time1")
val time2 = measureTimeMillis {
programAst.constantFold()
}
//println(" time2: $time2")
val time3 = measureTimeMillis {
programAst.removeNopsFlattenAnonScopes()
programAst.reorderStatements()
programAst.addTypecasts()
}
//println(" time3: $time3")
val time4 = measureTimeMillis {
programAst.checkValid(compilerOptions) // check if tree is valid
}
//println(" time4: $time4")
programAst.checkIdentifiers()
if (optimize) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements()
if (optsDone1 + optsDone2 == 0)
break
}
}
programAst.addTypecasts()
programAst.removeNopsFlattenAnonScopes()
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (optimize)
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly) {
// asm generation directly from the Ast, no need for intermediate code
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
programAst.anonscopeVarsCleanup()
val assembly = CompilationTarget.asmGenerator(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programName = assembly.name
}
success = true
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
}
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
@ -131,16 +70,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()
@ -177,3 +135,70 @@ 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)
errors.handle()
if (optsDone1 + optsDone2 == 0)
break
}
// because simplified statements and expressions could give rise to more constants that can be folded away:
programAst.constantFold(errors)
errors.handle()
}
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
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.prepareAsmVariablesAndReturns(errors)
errors.handle()
val assembly = CompilationTarget.asmGenerator(
programAst,
zeropage,
compilerOptions,
outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
return assembly.name
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}

View File

@ -15,7 +15,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE)
@ -28,9 +28,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
DataType.FLOAT -> {
if (options.floats) {
if(position!=null)
printWarning("allocated a large value (float) in zeropage", position)
errors.warn("allocated a large value (float) in zeropage", position)
else
printWarning("$scopedname: allocated a large value (float) in zeropage")
errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY)
5
} else throw CompilerException("floating point option not enabled")
}

View File

@ -0,0 +1,80 @@
package prog8.compiler.target
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
class AsmVariableAndReturnsPreparer(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)
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.InsertAfter(subroutine.statements.last(), 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
}
}

View File

@ -5,6 +5,7 @@ import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import java.nio.file.Path
internal interface CompilationTarget {
companion object {
lateinit var name: String

View File

@ -6,6 +6,8 @@ internal interface IAssemblyGenerator {
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
}
internal const val generatedLabelPrefix = "_prog8_label_"
internal interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions)

View File

@ -3,10 +3,11 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path
import kotlin.system.exitProcess
class AssemblyProgram(override val name: String, outputDir: Path): IAssemblyProgram {
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg")
private val binFile = outputDir.resolve("$name.bin")
@ -18,7 +19,7 @@ class AssemblyProgram(override val name: String, outputDir: Path): IAssemblyProg
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
val outFile = when(options.output) {
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating C-64 prg.")
@ -34,27 +35,39 @@ class AssemblyProgram(override val name: String, outputDir: Path): IAssemblyProg
val proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor()
if(result!=0) {
if (result != 0) {
System.err.println("assembler failed with returncode $result")
exitProcess(result)
}
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
private fun removeGeneratedLabelsFromMonlist() {
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
val lines = viceMonListFile.toFile().readLines()
viceMonListFile.toFile().outputStream().bufferedWriter().use {
for (line in lines) {
if(pattern.matchEntire(line)==null)
it.write(line+"\n")
}
}
}
private fun generateBreakpointList() {
// builds list of breakpoints, appends to monitor list file
val breakpoints = mutableListOf<String>()
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them
for(line in viceMonListFile.toFile().readLines()) {
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
for (line in viceMonListFile.toFile().readLines()) {
val match = pattern.matchEntire(line)
if(match!=null)
breakpoints.add("break \$" + match.groupValues[1])
if (match != null)
breakpoints.add("break \$" + match.groupValues[1])
}
val num = breakpoints.size
breakpoints.add(0, "; vice monitor breakpoint list now follows")
breakpoints.add(1, "; $num breakpoints have been defined")
breakpoints.add(2, "del")
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n")+"\n")
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
}
}

View File

@ -11,23 +11,24 @@ import prog8.compiler.target.IAssemblyGenerator
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.generatedLabelPrefix
import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature
import prog8.functions.FSignature
import java.math.RoundingMode
import java.nio.file.Path
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.ArrayDeque
import java.util.*
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 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)
@ -41,6 +42,7 @@ internal class AsmGen(private val program: Program,
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()
@ -96,11 +98,15 @@ internal class AsmGen(private val program: Program,
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
out(" tsx")
out(" stx prog8_lib.orig_stackpointer")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
out(" tsx")
out(" stx prog8_lib.orig_stackpointer")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.RAW -> {
@ -120,11 +126,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")
@ -137,7 +142,6 @@ internal class AsmGen(private val program: Program,
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out("")
}
private fun footer() {
@ -171,6 +175,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")
}
@ -178,7 +197,7 @@ internal class AsmGen(private val program: Program,
internal fun makeLabel(postfix: String): String {
generatedLabelSequenceNumber++
return "_prog8_label_${generatedLabelSequenceNumber}_$postfix"
return "${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
}
private fun outputSourceLine(node: Node) {
@ -216,7 +235,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)
@ -225,7 +244,9 @@ internal class AsmGen(private val program: Program,
&& variable.datatype != DataType.FLOAT
&& options.zeropage != ZeropageType.DONTUSE) {
try {
val address = zeropage.allocate(fullName, variable.datatype, null)
val errors = ErrorReporter()
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
errors.handle()
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
// make sure we add the var to the set of zpvars for this block
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
@ -290,7 +311,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))
@ -344,7 +372,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)
}
}
@ -358,7 +386,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
@ -378,17 +413,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()
@ -398,12 +438,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)
@ -565,7 +604,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)
@ -598,11 +639,13 @@ internal class AsmGen(private val program: Program,
is Continue -> out(" jmp ${loopContinueLabels.peek()}")
is Break -> out(" jmp ${loopEndLabels.peek()}")
is WhileLoop -> translate(stmt)
is ForeverLoop -> translate(stmt)
is RepeatLoop -> translate(stmt)
is WhenStatement -> translate(stmt)
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 -> throw AssemblyError("missing asm translation for $stmt")
}
}
@ -628,6 +671,19 @@ internal class AsmGen(private val program: Program,
}
}
private fun translate(stmt: ForeverLoop) {
val foreverLabel = makeLabel("forever")
val endLabel = makeLabel("foreverend")
loopEndLabels.push(endLabel)
loopContinueLabels.push(foreverLabel)
out(foreverLabel)
translate(stmt.body)
out(" jmp $foreverLabel")
out(endLabel)
loopEndLabels.pop()
loopContinueLabels.pop()
}
private fun translate(stmt: WhileLoop) {
val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend")
@ -729,7 +785,7 @@ internal class AsmGen(private val program: Program,
}
private fun translate(stmt: Label) {
out(stmt.name)
out("_${stmt.name}") // underscore prefix to make sure it's a local label
}
private fun translate(scope: AnonymousScope) {
@ -768,6 +824,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" -> {
@ -798,7 +875,14 @@ internal class AsmGen(private val program: Program,
private fun getJumpTarget(jmp: Jump): String {
return when {
jmp.identifier!=null -> asmIdentifierName(jmp.identifier)
jmp.identifier!=null -> {
val target = jmp.identifier.targetStatement(program.namespace)
val asmName = asmIdentifierName(jmp.identifier)
if(target is Label)
"_$asmName" // prefix with underscore to jump to local label
else
asmName
}
jmp.generatedLabel!=null -> jmp.generatedLabel
jmp.address!=null -> jmp.address.toHex()
else -> "????"
@ -839,7 +923,7 @@ internal class AsmGen(private val program: Program,
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FunctionSignature) =
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
internal fun translateFunctionCall(functionCall: FunctionCall) =

View File

@ -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
}

View File

@ -7,12 +7,12 @@ import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.VarDecl
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.toHex
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -58,8 +58,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(read.addressExpression)
TODO("read memory byte from result and put that in ${assign.target}")
throw AssemblyError("missing asm gen for memread assignment into ${assign.target}")
}
}
}
@ -117,9 +116,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.translateExpression(assign.value as FunctionCall)
assignFromEvalResult(assign.target)
}
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct 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")
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array assignment $assign")
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened ${assign.value.position}")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values ${assign.value.position}")
}
}
@ -199,14 +198,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
target.memoryAddress!=null -> {
TODO("assign address $sourceName to memory word $target")
throw AssemblyError("no asm gen for assign address $sourceName to memory word $target")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign address $sourceName to array $targetName [ $index ]")
throw AssemblyError("no asm gen for assign address $sourceName to array $targetName [ $index ]")
}
else -> TODO("assign address $sourceName to $target")
else -> throw AssemblyError("no asm gen for assign address $sourceName to $target")
}
}
@ -225,7 +224,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
target.memoryAddress!=null -> {
TODO("assign wordvar $sourceName to memory ${target.memoryAddress}")
throw AssemblyError("no asm gen for assign wordvar $sourceName to memory ${target.memoryAddress}")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
@ -236,7 +235,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
}
else -> TODO("assign wordvar to $target")
else -> throw AssemblyError("no asm gen for assign wordvar to $target")
}
}
@ -267,7 +266,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.translateExpression(index)
asmgen.out(" lda #<$targetName | ldy #>$targetName | jsr c64flt.pop_float_to_indexed_var")
}
else -> TODO("assign floatvar to $target")
else -> throw AssemblyError("no asm gen for assign floatvar to $target")
}
}
@ -313,12 +312,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta (+) +1
sty (+) +2
lda $sourceName
+ sta ${65535.toHex()} ; modified
+ sta ${'$'}ffff ; modified
""")
}
}
}
else -> TODO("assign bytevar to $target")
else -> throw AssemblyError("no asm gen for assign bytevar to $target")
}
}
@ -413,7 +412,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
}
else -> TODO("assign register $register to $target")
else -> throw AssemblyError("no asm gen for assign register $register to $target")
}
}
@ -427,31 +426,23 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val targetName = asmgen.asmIdentifierName(addressExpr)
when(register) {
Register.A -> asmgen.out("""
ldy $targetName
sty ${C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y
""")
ldy $targetName
sty (+) +1
ldy $targetName+1
sty (+) +2
+ sta ${'$'}ffff ; modified""")
Register.X -> asmgen.out("""
txa
ldy $targetName
sty ${C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y
""")
ldy $targetName
sty (+) +1
ldy $targetName+1
sty (+) +2
+ stx ${'$'}ffff ; modified""")
Register.Y -> asmgen.out("""
tya
ldy $targetName
sty ${C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y
""")
lda $targetName
sta (+) +1
lda $targetName+1
sta (+) +2
+ sty ${'$'}ffff ; modified""")
}
}
else -> {
@ -469,7 +460,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
+ sty ${65535.toHex()} ; modified
+ sty ${'$'}ffff ; modified
""")
}
}
@ -498,7 +489,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
target.memoryAddress!=null -> {
TODO("assign word $word to memory ${target.memoryAddress}")
throw AssemblyError("no asm gen for assign word $word to memory ${target.memoryAddress}")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
@ -516,7 +507,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta $targetName+1,y
""")
}
else -> TODO("assign word $word to $target")
else -> throw AssemblyError("no asm gen for assign word $word to $target")
}
}
@ -547,7 +538,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta $targetName,y
""")
}
else -> TODO("assign byte $byte to $target")
else -> throw AssemblyError("no asm gen for assign byte $byte to $target")
}
}
@ -584,23 +575,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} else {
asmgen.translateExpression(index)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
asl a
asl a
clc
adc $ESTACK_LO_HEX,x
tay
lda #0
sta $targetName,y
sta $targetName+1,y
sta $targetName+2,y
sta $targetName+3,y
sta $targetName+4,y
""") // TODO use a subroutine for this
lda #<${targetName}
sta ${C64Zeropage.SCRATCH_W1}
lda #>${targetName}
sta ${C64Zeropage.SCRATCH_W1 + 1}
jsr c64flt.set_0_array_float
""")
}
}
else -> TODO("assign float 0.0 to $target")
else -> throw AssemblyError("no asm gen for assign float 0.0 to $target")
}
} else {
// non-zero value
@ -641,26 +624,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} else {
asmgen.translateArrayIndexIntoA(targetArrayIdx)
asmgen.out("""
sta ${C64Zeropage.SCRATCH_REG}
asl a
asl a
clc
adc ${C64Zeropage.SCRATCH_REG}
tay
lda $constFloat
sta $arrayVarName,y
lda $constFloat+1
sta $arrayVarName+1,y
lda $constFloat+2
sta $arrayVarName+2,y
lda $constFloat+3
sta $arrayVarName+3,y
lda $constFloat+4
sta $arrayVarName+4,y
""") // TODO use a subroutine for this
lda #<${constFloat}
sta ${C64Zeropage.SCRATCH_W1}
lda #>${constFloat}
sta ${C64Zeropage.SCRATCH_W1 + 1}
lda #<${arrayVarName}
sta ${C64Zeropage.SCRATCH_W2}
lda #>${arrayVarName}
sta ${C64Zeropage.SCRATCH_W2 + 1}
jsr c64flt.set_array_float
""")
}
}
else -> TODO("assign float $float to $target")
else -> throw AssemblyError("no asm gen for assign float $float to $target")
}
}
}
@ -687,9 +663,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign memory byte at $address to array $targetName [ $index ]")
throw AssemblyError("no asm gen for assign memory byte at $address to array $targetName [ $index ]")
}
else -> TODO("assign memory byte $target")
else -> throw AssemblyError("no asm gen for assign memory byte $target")
}
}
else if(identifier!=null) {
@ -697,22 +673,25 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when {
target.register!=null -> {
asmgen.out("""
ldy #0
lda ($sourceName),y
""")
lda $sourceName
sta (+) + 1
lda $sourceName+1
sta (+) + 2""")
when(target.register){
Register.A -> {}
Register.X -> asmgen.out(" tax")
Register.Y -> asmgen.out(" tay")
Register.A -> asmgen.out("+ lda ${'$'}ffff\t; modified")
Register.X -> asmgen.out("+ ldx ${'$'}ffff\t; modified")
Register.Y -> asmgen.out("+ ldy ${'$'}ffff\t; modified")
}
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
ldy #0
lda ($sourceName),y
sta $targetName
""")
lda $sourceName
sta (+) + 1
lda $sourceName+1
sta (+) + 2
+ lda ${'$'}ffff\t; modified
sta $targetName""")
}
target.memoryAddress!=null -> {
asmgen.out(" ldy $sourceName")
@ -721,9 +700,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign memory byte $sourceName to array $targetName [ $index ]")
throw AssemblyError("no asm gen for assign memory byte $sourceName to array $targetName [ $index ]")
}
else -> TODO("assign memory byte $target")
else -> throw AssemblyError("no asm gen for assign memory byte $target")
}
}
}

View File

@ -3,13 +3,14 @@ package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.compiler.toHex
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
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS2_HEX
import prog8.compiler.toHex
import prog8.functions.BuiltinFunctions
import kotlin.math.absoluteValue
@ -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")
}
@ -126,8 +127,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)
@ -158,7 +167,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateExpression(expr: RegisterExpr) {
when(expr.register) {
Register.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register")
Register.X -> asmgen.out(" txa | sta $ESTACK_LO_HEX,x | dex")
Register.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
}
}
@ -201,10 +210,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 +248,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
}
"*" -> {
@ -224,8 +271,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(value!=null) {
if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt()
if(amount in powersOfTwo)
printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount")
when(rightDt) {
DataType.UBYTE -> {
if(amount in optimizedByteMultiplications) {

View File

@ -8,11 +8,11 @@ import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import kotlin.math.absoluteValue
// todo choose more efficient comparisons to avoid needless lda's

View File

@ -7,10 +7,10 @@ import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.toHex
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {

View File

@ -6,9 +6,9 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RegisterExpr
import prog8.ast.statements.PostIncrDecr
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.toHex
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -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)
}

View File

@ -7,88 +7,87 @@ import prog8.compiler.CompilerException
import kotlin.math.*
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
class FunctionSignature(val pure: Boolean, // does it have side effects?
val parameters: List<BuiltinFunctionParam>,
val returntype: DataType?,
val constExpressionFunc: ConstExpressionCaller? = null)
class FSignature(val pure: Boolean, // does it have side effects?
val parameters: List<FParam>,
val returntype: DataType?,
val constExpressionFunc: ConstExpressionCaller? = null)
val BuiltinFunctions = mapOf(
// this set of function have no return value and operate in-place:
"rol" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"sort" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
"reverse" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
"rol" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"lsl" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
"lsr" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// these few have a return value depending on the argument(s):
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
"max" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
"min" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
// normal functions follow:
"sgn" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
"sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FunctionSignature(true, listOf(
BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)),
BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
"rnd" to FunctionSignature(true, emptyList(), DataType.UBYTE),
"rndw" to FunctionSignature(true, emptyList(), DataType.UWORD),
"rndf" to FunctionSignature(true, emptyList(), DataType.FLOAT),
"rsave" to FunctionSignature(false, emptyList(), null),
"rrestore" to FunctionSignature(false, emptyList(), null),
"set_carry" to FunctionSignature(false, emptyList(), null),
"clear_carry" to FunctionSignature(false, emptyList(), null),
"set_irqd" to FunctionSignature(false, emptyList(), null),
"clear_irqd" to FunctionSignature(false, emptyList(), null),
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
"memcopy" to FunctionSignature(false, listOf(
BuiltinFunctionParam("from", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("to", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen)
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
"sqrt16" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"round" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FSignature(true, listOf(FParam("lsb", setOf(DataType.UBYTE)), FParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
"rnd" to FSignature(true, emptyList(), DataType.UBYTE),
"rndw" to FSignature(true, emptyList(), DataType.UWORD),
"rndf" to FSignature(true, emptyList(), DataType.FLOAT),
"exit" to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
"rsave" to FSignature(false, emptyList(), null),
"rrestore" to FSignature(false, emptyList(), null),
"set_carry" to FSignature(false, emptyList(), null),
"clear_carry" to FSignature(false, emptyList(), null),
"set_irqd" to FSignature(false, emptyList(), null),
"clear_irqd" to FSignature(false, emptyList(), null),
"read_flags" to FSignature(false, emptyList(), DataType.UBYTE),
"swap" to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
"memcopy" to FSignature(false, listOf(
FParam("from", IterableDatatypes + DataType.UWORD),
FParam("to", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FSignature(false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UWORD)),
FParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FSignature(false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numwords", setOf(DataType.UWORD)),
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen)
)
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!

View File

@ -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
@ -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,7 +126,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
override fun visit(decl: VarDecl) {
if (decl.autogeneratedDontRemove || (decl.definingModule().isLibraryModule && decl.type != VarDeclType.VAR)) {
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
// make sure autogenerated vardecls are in the used symbols
addNodeAndParentScopes(decl)
}

View File

@ -8,27 +8,16 @@ 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) : IAstModifyingVisitor {
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf()
private val reportedErrorMessages = mutableSetOf<String>()
fun addError(x: AstException) {
// check that we don't add the isSameAs error more than once
if(x.toString() !in reportedErrorMessages) {
reportedErrorMessages.add(x.toString())
errors.add(x)
}
}
override fun visit(decl: VarDecl): Statement {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call tree for this?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
errors.add(ExpressionError("recursive var declaration", decl.position))
errors.err("recursive var declaration", decl.position)
return decl
}
@ -69,7 +58,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
@ -88,7 +77,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
}
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.add(ExpressionError("arraysize requires only integers here", numericLv.position))
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.size() ?: return decl
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
@ -96,24 +85,24 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.add(ExpressionError("ubyte value overflow", numericLv.position))
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.add(ExpressionError("byte value overflow", numericLv.position))
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.add(ExpressionError("uword value overflow", numericLv.position))
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.add(ExpressionError("word value overflow", numericLv.position))
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
val 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
@ -131,7 +120,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("float value overflow", litval.position))
errors.err("float value overflow", litval.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
@ -150,6 +139,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
}
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)
}
@ -166,32 +162,22 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
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) {
addError(ax)
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) {
addError(ax)
functionCall
}
return functionCall.constValue(program) ?: functionCall
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
@ -247,46 +233,41 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
* 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) {
addError(ax)
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
}
/**
@ -307,45 +288,40 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
* (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) {
addError(ax)
expr
else -> expr
}
}
@ -643,72 +619,4 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
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
}
}

View File

@ -0,0 +1,617 @@
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
x + x -> x << 1 (for words... for bytes too?)
x + x + x + x -> x << 2 (for words... for bytes too?)
x + x + x -> ???? x*3 ??? words/bytes?
x - x -> 0
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))
}
}
}
}
// 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?)
}

View File

@ -1,34 +1,25 @@
package prog8.optimizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.parser.ParsingFailedError
import prog8.ast.base.ErrorReporter
internal fun Program.constantFold() {
val optimizer = ConstantFolding(this)
try {
optimizer.visit(this)
} catch (ax: AstException) {
optimizer.addError(ax)
}
internal fun Program.constantFold(errors: ErrorReporter) {
val optimizer = ConstantFoldingOptimizer(this, errors)
optimizer.visit(this)
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
while(errors.isEmpty() && optimizer.optimizationsDone>0) {
optimizer.optimizationsDone = 0
optimizer.visit(this)
}
if(optimizer.errors.isNotEmpty()) {
optimizer.errors.forEach { System.err.println(it) }
throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
} else {
if(errors.isEmpty())
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
}
}
internal fun Program.optimizeStatements(): Int {
val optimizer = StatementOptimizer(this)
internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
val optimizer = StatementOptimizer(this, errors)
optimizer.visit(this)
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
@ -36,7 +27,7 @@ internal fun Program.optimizeStatements(): 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()
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -2,12 +2,10 @@ 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.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
@ -15,12 +13,13 @@ import kotlin.math.floor
/*
TODO: remove unreachable code?
TODO: remove unreachable code after return and exit()
TODO: proper inlining of tiny subroutines (at first, restrict to subs without parameters and variables in them, and build it up from there: correctly renaming/relocating all variables in them and refs to those as well)
*/
internal class StatementOptimizer(private val program: Program) : IAstModifyingVisitor {
internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
private set
@ -80,13 +79,13 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
optimizationsDone++
printWarning("removing empty block '${block.name}'", block.position)
errors.warn("removing empty block '${block.name}'", block.position)
return NopStatement.insteadOf(block)
}
if (block !in callgraph.usedSymbols) {
optimizationsDone++
printWarning("removing unused block '${block.name}'", block.position)
errors.warn("removing unused block '${block.name}'", block.position)
return NopStatement.insteadOf(block) // remove unused block
}
}
@ -99,7 +98,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.containsNoCodeNorVars()) {
printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position)
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement.insteadOf(subroutine)
}
@ -111,7 +110,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement.insteadOf(subroutine)
}
@ -123,7 +122,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) {
if(decl.type == VarDeclType.VAR)
printWarning("removing unused variable ${decl.type} '${decl.name}'", decl.position)
errors.warn("removing unused variable ${decl.type} '${decl.name}'", decl.position)
optimizationsDone++
return NopStatement.insteadOf(decl)
}
@ -160,7 +159,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
optimizationsDone++
return NopStatement.insteadOf(functionCallStatement)
}
@ -263,12 +262,12 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
printWarning("condition is always true", ifStatement.position)
errors.warn("condition is always true", ifStatement.position)
optimizationsDone++
ifStatement.truepart
} else {
// always false -> keep only else-part
printWarning("condition is always false", ifStatement.position)
errors.warn("condition is always false", ifStatement.position)
optimizationsDone++
ifStatement.elsepart
}
@ -311,21 +310,13 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
val constvalue = whileLoop.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> print a warning, and optimize into body + jump (if there are no continue and break statements)
printWarning("condition is always true", whileLoop.condition.position)
if(hasContinueOrBreak(whileLoop.body))
return whileLoop
val backLabelName = "_prog8_back${whileLoop.position.line}"
val label = Label(backLabelName, whileLoop.condition.position)
whileLoop.body.statements.add(0, label)
whileLoop.body.statements.add(Jump(null,
IdentifierReference(listOf(backLabelName), whileLoop.condition.position),
null, whileLoop.condition.position))
// always true -> print a warning, and optimize into a forever-loop
errors.warn("condition is always true", whileLoop.condition.position)
optimizationsDone++
return whileLoop.body
ForeverLoop(whileLoop.body, whileLoop.position)
} else {
// always false -> ditch whole statement
printWarning("condition is always false", whileLoop.condition.position)
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
optimizationsDone++
NopStatement.insteadOf(whileLoop)
}
@ -339,7 +330,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only the statement block (if there are no continue and break statements)
printWarning("condition is always true", repeatLoop.untilCondition.position)
errors.warn("condition is always true", repeatLoop.untilCondition.position)
if(hasContinueOrBreak(repeatLoop.body))
repeatLoop
else {
@ -347,18 +338,10 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
repeatLoop.body
}
} else {
// always false -> print a warning, and optimize into body + jump (if there are no continue and break statements)
printWarning("condition is always false", repeatLoop.untilCondition.position)
if(hasContinueOrBreak(repeatLoop.body))
return repeatLoop
val backLabelName = "_prog8_back${repeatLoop.position.line}"
val label = Label(backLabelName, repeatLoop.untilCondition.position)
repeatLoop.body.statements.add(0, label)
repeatLoop.body.statements.add(Jump(null,
IdentifierReference(listOf(backLabelName), repeatLoop.untilCondition.position),
null, repeatLoop.untilCondition.position))
// always false -> print a warning, and optimize into a forever loop
errors.warn("condition is always false", repeatLoop.untilCondition.position)
optimizationsDone++
return repeatLoop.body
ForeverLoop(repeatLoop.body, repeatLoop.position)
}
}
return repeatLoop
@ -424,7 +407,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
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)) {
@ -571,7 +554,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
// remove duplicate labels
val stmts = label.definingScope().statements
val startIdx = stmts.indexOf(label)
if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label)
if(startIdx< stmts.lastIndex && stmts[startIdx+1] == label)
return NopStatement.insteadOf(label)
return super.visit(label)
@ -580,39 +563,3 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
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)
}
}

View File

@ -4,6 +4,7 @@ import org.antlr.v4.runtime.*
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.antlr.toAst
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.base.SyntaxError
import prog8.ast.base.checkImportedValid
@ -33,114 +34,117 @@ internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexe
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
internal fun importModule(program: Program, filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString()
if(importloc.startsWith(curdir))
importloc = "." + importloc.substring(curdir.length)
println(" (from '$importloc')")
}
else
println("")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
internal class ModuleImporter(private val errors: ErrorReporter) {
val input = CharStreams.fromPath(filePath)
return importModule(program, input, filePath, false)
}
internal fun importModule(program: Program, filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString()
if(importloc.startsWith(curdir))
importloc = "." + importloc.substring(curdir.length)
println(" (from '$importloc')")
}
else
println("")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
internal fun importLibraryModule(program: Program, name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(program, import, Paths.get(""))
}
internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener()
lexer.addErrorListener(lexerErrors)
val tokens = CommentHandlingTokenStream(lexer)
val parser = prog8Parser(tokens)
val parseTree = parser.module()
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
if(numberOfErrors > 0)
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
// You can do something with the parsed comments:
// tokens.commentTokens().forEach { println(it) }
// convert to Ast
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
moduleAst.program = program
moduleAst.linkParents(program.namespace)
program.modules.add(moduleAst)
// accept additional imports
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
moduleAst.statements = lines
return moduleAst
}
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8"
val locations = mutableListOf(source.parent)
val propPath = System.getProperty("prog8.libdir")
if(propPath!=null)
locations.add(pathFrom(propPath))
val envPath = System.getenv("PROG8_LIBDIR")
if(envPath!=null)
locations.add(pathFrom(envPath))
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
locations.forEach {
val file = pathFrom(it.toString(), fileName)
if (Files.isReadable(file)) return file
val input = CharStreams.fromPath(filePath)
return importModule(program, input, filePath, false)
}
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
}
internal fun importLibraryModule(program: Program, name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(program, import, Paths.get(""))
}
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener()
lexer.addErrorListener(lexerErrors)
val tokens = CommentHandlingTokenStream(lexer)
val parser = prog8Parser(tokens)
val parseTree = parser.module()
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
if(numberOfErrors > 0)
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null
// You can do something with the parsed comments:
// tokens.commentTokens().forEach { println(it) }
val resource = tryGetEmbeddedResource("$moduleName.p8")
val importedModule =
if(resource!=null) {
// load the module from the embedded resource
resource.use {
if(import.args[0].int==42)
println("importing '$moduleName' (library, auto)")
else
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
}
} else {
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
importModule(program, modulePath)
// convert to Ast
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
moduleAst.program = program
moduleAst.linkParents(program.namespace)
program.modules.add(moduleAst)
// accept additional imports
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
moduleAst.statements = lines
return moduleAst
}
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8"
val locations = mutableListOf(source.parent)
val propPath = System.getProperty("prog8.libdir")
if(propPath!=null)
locations.add(pathFrom(propPath))
val envPath = System.getenv("PROG8_LIBDIR")
if(envPath!=null)
locations.add(pathFrom(envPath))
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
locations.forEach {
val file = pathFrom(it.toString(), fileName)
if (Files.isReadable(file)) return file
}
importedModule.checkImportedValid()
return importedModule
}
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
}
internal fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null
val resource = tryGetEmbeddedResource("$moduleName.p8")
val importedModule =
if(resource!=null) {
// load the module from the embedded resource
resource.use {
if(import.args[0].int==42)
println("importing '$moduleName' (library, auto)")
else
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
}
} else {
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
importModule(program, modulePath)
}
importedModule.checkImportedValid()
return importedModule
}
private fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
}
}

View File

@ -0,0 +1,22 @@
package prog8.server.dbus
//import org.freedesktop.dbus.interfaces.DBusInterface
//
//
//interface IrmenDbusTest: DBusInterface
//{
// fun Status(address: String): Map<Int, String>
//}
//
//
//internal class TestService: IrmenDbusTest {
// override fun Status(address: String): Map<Int, String> {
// return mapOf(
// 5 to "hello",
// 42 to address
// )
// }
//
// override fun isRemote() = true
// override fun getObjectPath() = "/razorvine/TestService"
//}

View File

@ -0,0 +1,17 @@
package prog8.server.dbus
//import org.freedesktop.dbus.connections.impl.DBusConnection
//
//
//fun main() {
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
// println(it.names.toList())
// println(it.uniqueName)
// println(it.address)
// println(it.machineId)
// val obj = it.getRemoteObject("local.net.razorvine.dbus.test", "/razorvine/TestService", IrmenDbusTest::class.java)
// println(obj.Status("irmen"))
// }
//}
//

View File

@ -0,0 +1,18 @@
package prog8.server.dbus
//import org.freedesktop.dbus.connections.impl.DBusConnection
//
//
//fun main() {
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
// it.requestBusName("local.net.razorvine.dbus.test")
// println(it.names.toList())
// println(it.uniqueName)
// println(it.address)
// println(it.machineId)
// val service = TestService()
// it.exportObject(service.objectPath, service)
//
// Thread.sleep(100000)
// }
//}

View File

@ -1,658 +0,0 @@
package prog8.vm
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.vm.astvm.VmExecutionException
import java.util.Objects
import kotlin.math.abs
import kotlin.math.pow
/**
* Rather than a literal value (NumericLiteralValue) that occurs in the parsed source code,
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form)
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
*/
abstract class RuntimeValueBase(val type: DataType) {
abstract fun numericValue(): Number
abstract fun integerValue(): Int
}
class RuntimeValueNumeric(type: DataType, num: Number): RuntimeValueBase(type) {
val byteval: Short?
val wordval: Int?
val floatval: Double?
val asBoolean: Boolean
companion object {
fun fromLv(literalValue: NumericLiteralValue): RuntimeValueNumeric {
return RuntimeValueNumeric(literalValue.type, num = literalValue.number)
}
}
init {
when (type) {
DataType.UBYTE -> {
val inum = num.toInt()
require(inum in 0..255) { "invalid value for ubyte: $inum" }
byteval = inum.toShort()
wordval = null
floatval = null
asBoolean = byteval != 0.toShort()
}
DataType.BYTE -> {
val inum = num.toInt()
require(inum in -128..127) { "invalid value for byte: $inum" }
byteval = inum.toShort()
wordval = null
floatval = null
asBoolean = byteval != 0.toShort()
}
DataType.UWORD -> {
val inum = num.toInt()
require(inum in 0..65535) { "invalid value for uword: $inum" }
wordval = inum
byteval = null
floatval = null
asBoolean = wordval != 0
}
DataType.WORD -> {
val inum = num.toInt()
require(inum in -32768..32767) { "invalid value for word: $inum" }
wordval = inum
byteval = null
floatval = null
asBoolean = wordval != 0
}
DataType.FLOAT -> {
floatval = num.toDouble()
byteval = null
wordval = null
asBoolean = floatval != 0.0
}
else -> throw VmExecutionException("not a numeric value")
}
}
override fun toString(): String {
return when (type) {
DataType.UBYTE -> "ub:%02x".format(byteval)
DataType.BYTE -> {
if (byteval!! < 0)
"b:-%02x".format(abs(byteval.toInt()))
else
"b:%02x".format(byteval)
}
DataType.UWORD -> "uw:%04x".format(wordval)
DataType.WORD -> {
if (wordval!! < 0)
"w:-%04x".format(abs(wordval))
else
"w:%04x".format(wordval)
}
DataType.FLOAT -> "f:$floatval"
else -> "???"
}
}
override fun numericValue(): Number {
return when (type) {
in ByteDatatypes -> byteval!!
in WordDatatypes -> wordval!!
DataType.FLOAT -> floatval!!
else -> throw ArithmeticException("invalid datatype for numeric value: $type")
}
}
override fun integerValue(): Int {
return when (type) {
in ByteDatatypes -> byteval!!.toInt()
in WordDatatypes -> wordval!!
DataType.FLOAT -> throw ArithmeticException("float to integer loss of precision")
else -> throw ArithmeticException("invalid datatype for integer value: $type")
}
}
override fun hashCode(): Int = Objects.hash(byteval, wordval, floatval, type)
override fun equals(other: Any?): Boolean {
if (other == null || other !is RuntimeValueNumeric)
return false
return compareTo(other) == 0 // note: datatype doesn't matter
}
operator fun compareTo(other: RuntimeValueNumeric): Int = numericValue().toDouble().compareTo(other.numericValue().toDouble())
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): RuntimeValueNumeric {
if (leftDt != rightDt)
throw ArithmeticException("left and right datatypes are not the same")
if (result.toDouble() < 0) {
return when (leftDt) {
DataType.UBYTE, DataType.UWORD -> {
// storing a negative number in an unsigned one is done by storing the 2's complement instead
val number = abs(result.toDouble().toInt())
if (leftDt == DataType.UBYTE)
RuntimeValueNumeric(DataType.UBYTE, (number xor 255) + 1)
else
RuntimeValueNumeric(DataType.UWORD, (number xor 65535) + 1)
}
DataType.BYTE -> {
val v = result.toInt() and 255
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
else
RuntimeValueNumeric(DataType.BYTE, v - 256)
}
DataType.WORD -> {
val v = result.toInt() and 65535
if (v < 32768)
RuntimeValueNumeric(DataType.WORD, v)
else
RuntimeValueNumeric(DataType.WORD, v - 65536)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
return when (leftDt) {
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result.toInt() and 255)
DataType.BYTE -> {
val v = result.toInt() and 255
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
else
RuntimeValueNumeric(DataType.BYTE, v - 256)
}
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, result.toInt() and 65535)
DataType.WORD -> {
val v = result.toInt() and 65535
if (v < 32768)
RuntimeValueNumeric(DataType.WORD, v)
else
RuntimeValueNumeric(DataType.WORD, v - 65536)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
fun add(other: RuntimeValueNumeric): RuntimeValueNumeric {
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() + v2.toDouble()
return arithResult(type, result, other.type, "add")
}
fun sub(other: RuntimeValueNumeric): RuntimeValueNumeric {
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() - v2.toDouble()
return arithResult(type, result, other.type, "sub")
}
fun mul(other: RuntimeValueNumeric): RuntimeValueNumeric {
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() * v2.toDouble()
return arithResult(type, result, other.type, "mul")
}
fun div(other: RuntimeValueNumeric): RuntimeValueNumeric {
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
if (v2.toDouble() == 0.0) {
when (type) {
DataType.UBYTE -> return RuntimeValueNumeric(DataType.UBYTE, 255)
DataType.BYTE -> return RuntimeValueNumeric(DataType.BYTE, 127)
DataType.UWORD -> return RuntimeValueNumeric(DataType.UWORD, 65535)
DataType.WORD -> return RuntimeValueNumeric(DataType.WORD, 32767)
else -> {
}
}
}
val result = v1.toDouble() / v2.toDouble()
// NOTE: integer division returns integer result!
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result)
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, result)
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, result)
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, result)
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
else -> throw ArithmeticException("div on non-numeric type")
}
}
fun remainder(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() % v2.toDouble()
return arithResult(type, result, other.type, "remainder")
}
fun pow(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble())
return arithResult(type, result, other.type, "pow")
}
fun shl(): RuntimeValueNumeric {
val v = integerValue()
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(type, (v shl 1) and 255)
DataType.UWORD -> RuntimeValueNumeric(type, (v shl 1) and 65535)
DataType.BYTE -> {
val value = v shl 1
if (value < 128)
RuntimeValueNumeric(type, value)
else
RuntimeValueNumeric(type, value - 256)
}
DataType.WORD -> {
val value = v shl 1
if (value < 32768)
RuntimeValueNumeric(type, value)
else
RuntimeValueNumeric(type, value - 65536)
}
else -> throw ArithmeticException("invalid type for shl: $type")
}
}
fun shr(): RuntimeValueNumeric {
val v = integerValue()
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(type, v ushr 1)
DataType.BYTE -> RuntimeValueNumeric(type, v shr 1)
DataType.UWORD -> RuntimeValueNumeric(type, v ushr 1)
DataType.WORD -> RuntimeValueNumeric(type, v shr 1)
else -> throw ArithmeticException("invalid type for shr: $type")
}
}
fun rol(carry: Boolean): Pair<RuntimeValueNumeric, Boolean> {
// 9 or 17 bit rotate left (with carry))
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if (carry) 1 else 0)
Pair(RuntimeValueNumeric(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val newCarry = (v and 0x8000) != 0
val newval = (v and 0x7fff shl 1) or (if (carry) 1 else 0)
Pair(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("rol can only work on byte/word")
}
}
fun ror(carry: Boolean): Pair<RuntimeValueNumeric, Boolean> {
// 9 or 17 bit rotate right (with carry)
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if (carry) 0x80 else 0)
Pair(RuntimeValueNumeric(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if (carry) 0x8000 else 0)
Pair(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun rol2(): RuntimeValueNumeric {
// 8 or 16 bit rotate left
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry
RuntimeValueNumeric(DataType.UBYTE, newval)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val carry = (v and 0x8000) ushr 15
val newval = (v and 0x7fff shl 1) or carry
RuntimeValueNumeric(DataType.UWORD, newval)
}
else -> throw ArithmeticException("rol2 can only work on byte/word")
}
}
fun ror2(): RuntimeValueNumeric {
// 8 or 16 bit rotate right
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = v and 1 shl 7
val newval = (v ushr 1) or carry
RuntimeValueNumeric(DataType.UBYTE, newval)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val carry = v and 1 shl 15
val newval = (v ushr 1) or carry
RuntimeValueNumeric(DataType.UWORD, newval)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun neg(): RuntimeValueNumeric {
return when (type) {
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, -(byteval!!))
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, -(wordval!!))
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, -(floatval)!!)
else -> throw ArithmeticException("neg can only work on byte/word/float")
}
}
fun abs(): RuntimeValueNumeric {
return when (type) {
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, abs(byteval!!.toInt()))
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, abs(wordval!!))
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, abs(floatval!!))
else -> throw ArithmeticException("abs can only work on byte/word/float")
}
}
fun bitand(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 and v2
return RuntimeValueNumeric(type, result)
}
fun bitor(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 or v2
return RuntimeValueNumeric(type, result)
}
fun bitxor(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 xor v2
return RuntimeValueNumeric(type, result)
}
fun and(other: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean && other.asBoolean) 1 else 0)
fun or(other: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean || other.asBoolean) 1 else 0)
fun xor(other: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean xor other.asBoolean) 1 else 0)
fun not() = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean) 0 else 1)
fun inv(): RuntimeValueNumeric {
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(type, byteval!!.toInt().inv() and 255)
DataType.UWORD -> RuntimeValueNumeric(type, wordval!!.inv() and 65535)
DataType.BYTE -> RuntimeValueNumeric(type, byteval!!.toInt().inv())
DataType.WORD -> RuntimeValueNumeric(type, wordval!!.inv())
else -> throw ArithmeticException("inv can only work on byte/word")
}
}
fun inc(): RuntimeValueNumeric {
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(type, (byteval!! + 1) and 255)
DataType.UWORD -> RuntimeValueNumeric(type, (wordval!! + 1) and 65535)
DataType.BYTE -> {
val newval = byteval!! + 1
if (newval == 128)
RuntimeValueNumeric(type, -128)
else
RuntimeValueNumeric(type, newval)
}
DataType.WORD -> {
val newval = wordval!! + 1
if (newval == 32768)
RuntimeValueNumeric(type, -32768)
else
RuntimeValueNumeric(type, newval)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, floatval!! + 1)
else -> throw ArithmeticException("inc can only work on numeric types")
}
}
fun dec(): RuntimeValueNumeric {
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(type, (byteval!! - 1) and 255)
DataType.UWORD -> RuntimeValueNumeric(type, (wordval!! - 1) and 65535)
DataType.BYTE -> {
val newval = byteval!! - 1
if (newval == -129)
RuntimeValueNumeric(type, 127)
else
RuntimeValueNumeric(type, newval)
}
DataType.WORD -> {
val newval = wordval!! - 1
if (newval == -32769)
RuntimeValueNumeric(type, 32767)
else
RuntimeValueNumeric(type, newval)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, floatval!! - 1)
else -> throw ArithmeticException("dec can only work on numeric types")
}
}
fun msb(): RuntimeValueNumeric {
return when (type) {
in ByteDatatypes -> RuntimeValueNumeric(DataType.UBYTE, 0)
in WordDatatypes -> RuntimeValueNumeric(DataType.UBYTE, wordval!! ushr 8 and 255)
else -> throw ArithmeticException("msb can only work on (u)byte/(u)word")
}
}
fun cast(targetType: DataType): RuntimeValueNumeric {
return when (type) {
DataType.UBYTE -> {
when (targetType) {
DataType.UBYTE -> this
DataType.BYTE -> {
val nval = byteval!!.toInt()
if (nval < 128)
RuntimeValueNumeric(DataType.BYTE, nval)
else
RuntimeValueNumeric(DataType.BYTE, nval - 256)
}
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, numericValue())
DataType.WORD -> {
val nval = numericValue().toInt()
if (nval < 32768)
RuntimeValueNumeric(DataType.WORD, nval)
else
RuntimeValueNumeric(DataType.WORD, nval - 65536)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.BYTE -> {
when (targetType) {
DataType.BYTE -> this
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, integerValue())
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.UWORD -> {
when (targetType) {
DataType.BYTE -> {
val v = integerValue()
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
else
RuntimeValueNumeric(DataType.BYTE, v - 256)
}
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> this
DataType.WORD -> {
val v = integerValue()
if (v < 32768)
RuntimeValueNumeric(DataType.WORD, v)
else
RuntimeValueNumeric(DataType.WORD, v - 65536)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.WORD -> {
when (targetType) {
DataType.BYTE -> {
val v = integerValue() and 255
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
else
RuntimeValueNumeric(DataType.BYTE, v - 256)
}
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 65535)
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue())
DataType.WORD -> this
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.FLOAT -> {
when (targetType) {
DataType.BYTE -> {
val integer = numericValue().toInt()
if (integer in -128..127)
RuntimeValueNumeric(DataType.BYTE, integer)
else
throw ArithmeticException("overflow when casting float to byte: $this")
}
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, numericValue().toInt())
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, numericValue().toInt())
DataType.WORD -> {
val integer = numericValue().toInt()
if (integer in -32768..32767)
RuntimeValueNumeric(DataType.WORD, integer)
else
throw ArithmeticException("overflow when casting float to word: $this")
}
DataType.FLOAT -> this
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
}
class RuntimeValueString(val str: String, val 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

View File

@ -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")
}
}
}

View File

@ -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))
}
}

View File

@ -1,184 +0,0 @@
package prog8.vm.astvm
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import java.awt.*
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import java.awt.image.BufferedImage
import java.util.ArrayDeque
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.Timer
class BitmapScreenPanel : KeyListener, JPanel() {
private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB)
private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0
private var cursorY: Int=0
val keyboardBuffer = ArrayDeque<Char>()
init {
val size = Dimension(image.width * SCALING, image.height * SCALING)
minimumSize = size
maximumSize = size
preferredSize = size
clearScreen(6)
isFocusable = true
requestFocusInWindow()
addKeyListener(this)
}
override fun keyTyped(p0: KeyEvent) {
keyboardBuffer.add(p0.keyChar)
}
override fun keyPressed(p0: KeyEvent) {
}
override fun keyReleased(p0: KeyEvent?) {
}
override fun paint(graphics: Graphics?) {
val g2d = graphics as Graphics2D?
g2d!!.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF)
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE)
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
g2d.drawImage(image, 0, 0, image.width * 3, image.height * 3, null)
}
fun clearScreen(color: Short) {
g2d.background = C64MachineDefinition.colorPalette[color % C64MachineDefinition.colorPalette.size]
g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT)
cursorX = 0
cursorY = 0
}
fun setPixel(x: Int, y: Int, color: Short) {
image.setRGB(x, y, C64MachineDefinition.colorPalette[color % C64MachineDefinition.colorPalette.size].rgb)
}
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) {
g2d.color = C64MachineDefinition.colorPalette[color % C64MachineDefinition.colorPalette.size]
g2d.drawLine(x1, y1, x2, y2)
}
fun printAsciiText(text: String) {
val t2 = text.substringBefore(0.toChar())
val petscii = Petscii.encodePetscii(t2, true)
petscii.forEach { printPetsciiChar(it) }
}
fun printPetsciiChar(petscii: Short) {
if(petscii in listOf(0x0d.toShort(), 0x8d.toShort())) {
// Return and shift-Return
cursorX=0
cursorY++
} else {
val scr = Petscii.petscii2scr(petscii, false)
setScreenChar(cursorX, cursorY, scr, 1)
cursorX++
if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++
cursorX = 0
}
}
while(cursorY>=(SCREENHEIGHT/8)) {
// scroll the screen up because the cursor went past the last line
Thread.sleep(10)
val screen = image.copy()
val graphics = image.graphics as Graphics2D
graphics.drawImage(screen, 0, -8, null)
val color = graphics.color
graphics.color = C64MachineDefinition.colorPalette[6]
graphics.fillRect(0, 24*8, SCREENWIDTH, 25*8)
graphics.color=color
cursorY--
}
}
fun setScreenChar(x: Int, y: Int, screencode: Short, color: Short) {
g2d.clearRect(8*x, 8*y, 8, 8)
val colorIdx = (color % C64MachineDefinition.colorPalette.size).toShort()
val coloredImage = C64MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null)
}
fun setCursorPos(x: Int, y: Int) {
cursorX = x
cursorY = y
}
fun getCursorPos(): Pair<Int, Int> {
return Pair(cursorX, cursorY)
}
companion object {
const val SCREENWIDTH = 320
const val SCREENHEIGHT = 200
const val SCALING = 3
}
}
class ScreenDialog(title: String) : JFrame(title) {
val canvas = BitmapScreenPanel()
val keyboardBuffer = canvas.keyboardBuffer
init {
val borderWidth = 16
layout = GridBagLayout()
defaultCloseOperation = EXIT_ON_CLOSE
isResizable = false
// the borders (top, left, right, bottom)
val borderTop = JPanel().apply {
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = C64MachineDefinition.colorPalette[14]
}
val borderBottom = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = C64MachineDefinition.colorPalette[14]
}
val borderLeft = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = C64MachineDefinition.colorPalette[14]
}
val borderRight = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = C64MachineDefinition.colorPalette[14]
}
var c = GridBagConstraints()
c.gridx=0; c.gridy=1; c.gridwidth=3
add(borderTop, c)
c = GridBagConstraints()
c.gridx=0; c.gridy=2
add(borderLeft, c)
c = GridBagConstraints()
c.gridx=2; c.gridy=2
add(borderRight, c)
c = GridBagConstraints()
c.gridx=0; c.gridy=3; c.gridwidth=3
add(borderBottom, c)
// the screen canvas(bitmap)
c = GridBagConstraints()
c.gridx = 1; c.gridy = 2
add(canvas, c)
canvas.requestFocusInWindow()
}
fun start() {
val repaintTimer = Timer(1000 / 60) { repaint() }
repaintTimer.start()
}
}
private fun BufferedImage.copy(): BufferedImage {
val bcopy = BufferedImage(this.width, this.height, this.type)
val g = bcopy.graphics
g.drawImage(this, 0, 0, null)
g.dispose()
return bcopy
}

View File

@ -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)
}
}

View File

@ -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())
}
}

View File

@ -6,6 +6,7 @@ import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
@ -15,7 +16,6 @@ import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValueNumeric
import java.io.CharConversionException
import kotlin.test.*
@ -124,31 +124,34 @@ class TestCompiler {
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestZeropage {
private val errors = ErrorReporter()
@Test
fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null)
zp.allocate("varname", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("varname", DataType.UBYTE, null, errors)
assertFailsWith<AssertionError> {
zp.allocate("varname", DataType.UBYTE, null)
zp.allocate("varname", DataType.UBYTE, null, errors)
}
zp.allocate("varname2", DataType.UBYTE, null)
zp.allocate("varname2", DataType.UBYTE, null, errors)
}
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null)
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null)
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
zp3.allocate("", DataType.FLOAT, null)
zp3.allocate("", DataType.FLOAT, null, errors)
}
@Test
@ -173,7 +176,7 @@ class TestZeropage {
println(zp.free)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
zp.allocate("", DataType.BYTE, null)
zp.allocate("", DataType.BYTE, null, errors)
}
}
@ -218,19 +221,19 @@ class TestZeropage {
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free
zp.allocate("", DataType.FLOAT, null)
zp.allocate("", DataType.FLOAT, null, errors)
}
for (i in 0 until zp.available()) {
val loc = zp.allocate("", DataType.UBYTE, null)
val loc = zp.allocate("", DataType.UBYTE, null, errors)
assertTrue(loc > 0)
}
assertEquals(0, zp.available())
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null, errors)
}
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UWORD, null)
zp.allocate("", DataType.UWORD, null, errors)
}
}
@ -238,29 +241,29 @@ class TestZeropage {
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.UWORD, null)
val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3)
assertFalse(loc in zp.free)
val num = zp.available() / 2
for(i in 0..num-4) {
zp.allocate("", DataType.UWORD, null)
zp.allocate("", DataType.UWORD, null, errors)
}
assertEquals(6,zp.available())
assertFailsWith<ZeropageDepletedError> {
// can't allocate because no more sequential bytes, only fragmented
zp.allocate("", DataType.UWORD, null)
zp.allocate("", DataType.UWORD, null, errors)
}
for(i in 0..5) {
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null, errors)
}
assertEquals(0, zp.available())
assertFailsWith<ZeropageDepletedError> {
// no more space
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null, errors)
}
}
@ -268,16 +271,16 @@ class TestZeropage {
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
assertEquals(16, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x94, zp.allocate("", DataType.UWORD, null))
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null))
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null))
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0, zp.available())
}
}
@ -352,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)
@ -369,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)
}
}

View File

@ -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.

View File

@ -135,30 +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)
- 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``
- 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:
@ -167,26 +166,25 @@ Required tools
--------------
`64tass <https://sourceforge.net/projects/tass64/>`_ - cross assembler. Install this on your shell path.
A recent .exe version of this tool for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
For other platforms it is very easy to compile it yourself (make ; make install).
It's very easy to compile yourself.
A recent precompiled .exe for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
A **Java runtime (jre or jdk), version 8 or newer** is required to run the packaged compiler.
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead
and for Windows it's possible to get that as well. Check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ for
downloads.
A **Java runtime (jre or jdk), version 8 or newer** is required to run the prog8 compiler itself.
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead.
Fnd for Windows it's possible to get that as well. Check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
Finally: a **C-64 emulator** (or a real C-64 ofcourse) to run the programs on. The compiler assumes the presence
of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
Finally: a **C-64 emulator** (or a real C-64 ofcourse) can be nice to test and run your programs on.
The compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
.. important::
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
(re)building the compiler itself requires a recent Kotlin SDK.
The compiler is developed using the `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_
IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available).
But a bare Kotlin SDK installation should work just as well.
(Re)building the compiler itself requires a recent Kotlin SDK.
The compiler is developed using `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_ ,
but only a Kotlin SDK installation should work as well, because the gradle tool is
used to compile everything from the commandline.
Instructions on how to obtain a working compiler are in :ref:`building_compiler`.
Instructions on how to obtain a prebuilt compiler are in :ref:`building_compiler`.
.. toctree::

View File

@ -12,57 +12,56 @@ Elements of a program
---------------------
Program
Consists of one or more *modules*.
Consists of one or more *modules*.
Module
A file on disk with the ``.p8`` suffix. It contains *directives* and *code blocks*.
Whitespace and indentation in the source code are arbitrary and can be tabs or spaces or both.
You can also add *comments* to the source code.
One moudule file can *import* others, and also import *library modules*.
A file on disk with the ``.p8`` suffix. It can contain *directives* and *code blocks*.
Whitespace and indentation in the source code are arbitrary and can be mixed tabs or spaces.
A module file can *import* other modules, including *library modules*.
Comments
Everything after a semicolon ``;`` is a comment and is ignored by the compiler.
If the whole line is just a comment, it will be copied into the resulting assembly source code.
This makes it easier to understand and relate the generated code. Examples::
A = 42 ; set the initial value to 42
; next is the code that...
Everything after a semicolon ``;`` is a comment and is ignored by the compiler.
If the whole line is just a comment, this line will be copied into the resulting assembly source code for reference.
Directive
These are special instructions for the compiler, to change how it processes the code
and what kind of program it creates. A directive is on its own line in the file, and
starts with ``%``, optionally followed by some arguments.
These are special instructions for the compiler, to change how it processes the code
and what kind of program it creates. A directive is on its own line in the file, and
starts with ``%``, optionally followed by some arguments.
Code block
A block of actual program code. It defines a *scope* (also known as 'namespace') and
can contain Prog8 *code*, *variable declarations* and *subroutines*.
More details about this below: :ref:`blocks`.
A block of actual program code. It has a starting address in memory,
and defines a *scope* (also known as 'namespace').
It contains variables and subroutines.
More details about this below: :ref:`blocks`.
Variable declarations
The data that the code works on is stored in variables ('named values that can change').
The compiler allocates the required memory for them.
There is *no dynamic memory allocation*. The storage size of all variables
is fixed and is determined at compile time.
Variable declarations tend to appear at the top of the code block that uses them.
They define the name and type of the variable, and its initial value.
Prog8 supports a small list of data types, including special 'memory mapped' types
that don't allocate storage but instead point to a fixed location in the address space.
The data that the code works on is stored in variables ('named values that can change').
The compiler allocates the required memory for them.
There is *no dynamic memory allocation*. The storage size of all variables
is fixed and is determined at compile time.
Variable declarations tend to appear at the top of the code block that uses them.
They define the name and type of the variable, and its initial value.
Prog8 supports a small list of data types, including special 'memory mapped' types
that don't allocate storage but instead point to a fixed location in the address space.
Code
These are the instructions that make up the program's logic. There are different kinds of instructions
('statements' is a better name):
These are the instructions that make up the program's logic.
Code can only occur inside a subroutine.
There are different kinds of instructions ('statements' is a better name) such as:
- value assignment
- looping (for, while, repeat, unconditional jumps)
- conditional execution (if - then - else, when, and conditional jumps)
- subroutine calls
- label definition
- value assignment
- looping (for, while, repeat, unconditional jumps)
- conditional execution (if - then - else, when, and conditional jumps)
- subroutine calls
- label definition
Subroutine
Defines a piece of code that can be called by its name from different locations in your code.
It accepts parameters and can return a value (optional).
It can define its own variables, and it is even possible to define subroutines nested inside other subroutines.
Their contents is scoped accordingly.
Nested subroutines can access the variables from outer scopes.
This removes the need and overhead to pass everything via parameters.
Label
This is a named position in your code where you can jump to from another place.
@ -90,16 +89,20 @@ Scope
Blocks, Scopes, and accessing Symbols
-------------------------------------
**Blocks** are the top level separate pieces of code and data of your program. They are combined
into a single output program. No code or data can occur outside a block. Here's an example::
**Blocks** are the top level separate pieces of code and data of your program. They have a
starting address in memory and will be combined together into a single output program.
They can only contain *directives*, *variable declarations*, *subroutines* and *inline assembly code*.
Your actual program code can only exist inside these subroutines.
(except the occasional inline assembly)
main $c000 {
; this is code inside the block...
}
Here's an example::
main $c000 {
; this is code inside the block...
}
The name of a block must be unique in your entire program.
Also be careful when importing other modules; blocks in your own code cannot have
Be careful when importing other modules; blocks in your own code cannot have
the same name as a block defined in an imported module or library.
If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed).
@ -109,7 +112,7 @@ want to work on later, because the contents of the ignored block are not fully p
The address can be used to place a block at a specific location in memory.
Usually it is omitted, and the compiler will automatically choose the location (usually immediately after
the previous block in memory).
The address must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$200`` is the cpu stack).
It must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$1ff`` is the cpu stack).
.. _scopes:
@ -132,15 +135,18 @@ Scopes are created using either of these two statements:
- blocks (top-level named scope)
- subroutines (nested named scope)
.. note::
In contrast to 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 is a bit restrictive because 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 the
target system is very limited and it would be a waste to allocate a lot of variables.
Right now the prog8 compiler is not advanced enough to be able to 'share' or 'overlap'
variables intelligently by itself. So for now, it's something the programmer has to think about.
.. 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 you have to think about yourself.
Program Start and Entry Point
@ -150,21 +156,14 @@ 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 {
sub start () {
; program entrypoint code here
return
}
}
main {
sub start () {
; program entrypoint code here
return
}
}
The ``main`` module is always relocated to the start of your programs
@ -175,12 +174,11 @@ calls with the SYS statement.
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::
@ -374,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
@ -386,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
-----
@ -406,6 +393,10 @@ Iterating with a floating point variable is not supported. If you want to loop o
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
The *repeat--until* loop is used to repeat a piece of code until a certain condition is true.
The *forever*-loop is used to simply run a piece of code in a loop, forever. You can still
break out of this loop if desired. A "while true" or "until false" loop is equivalent to
a forever-loop.
You can also create loops by using the ``goto`` statement, but this should usually be avoided.
.. attention::
@ -836,6 +827,10 @@ rrestore()
read_flags()
Returns the current value of the CPU status register.
exit(returncode)
Immediately stops the program and exits it, with the returncode in the A register.
Note: custom interrupt handlers remain active unless manually cleared first!
Library routines

View File

@ -172,13 +172,13 @@ Code blocks
-----------
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
can contain Prog8 *code*, *directives*, *variable declarations* and *subroutines*::
can only contain *directives*, *variable declarations*, *subroutines* or *inline assembly*::
<blockname> [<address>] {
<directives>
<variables>
<statements>
<subroutines>
<inline asm>
}
The <blockname> must be a valid identifier.
@ -191,7 +191,6 @@ Also read :ref:`blocks`. Here is an example of a code block, to be loaded at ``
}
Labels
------
@ -217,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::
@ -322,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
@ -598,8 +598,8 @@ You can use a single statement, or a statement block like in the example below::
}
repeat--until loop
^^^^^^^^^^^^^^^^^^
repeat-until loop
^^^^^^^^^^^^^^^^^
Until the given condition is true (1), repeat the given statement(s).
You can use a single statement, or a statement block like in the example below::
@ -611,6 +611,19 @@ You can use a single statement, or a statement block like in the example below::
} until <condition>
forever loop
^^^^^^^^^^^^
Simply run the code in a loop, forever. It's the same as a while true or until false loop,
or just a jump back to a previous label. You can still break out of this loop as well, if you want::
forever {
; .. do stuff
if something
break ; you can exit the loop if you want
}
Conditional Execution and Jumps
-------------------------------

View File

@ -2,7 +2,10 @@
TODO
====
- option to load library files from a directory instead of the embedded ones
- 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/
Memory Block Operations integrated in language?
@ -26,12 +29,15 @@ More optimizations
Add more compiler optimizations to the existing ones.
- on the language AST level
- on the final assembly source level
- 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
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
- more optimizations on the language AST level
- more optimizations on the final assembly source level
Eval stack redesign? (lot of work)
@ -43,13 +49,14 @@ It could then even be moved into the zeropage to greatly reduce code size and sl
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 improvde code size and speed for operatios 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

View File

@ -104,7 +104,17 @@ main {
ub = all(farr)
if ub==0 c64scr.print("error all10\n")
check_eval_stack()
c64scr.print("\nyou should see no errors above.")
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@ main {
div_float(0,1,0)
div_float(999.9,111.0,9.008108108108107)
check_eval_stack()
}
sub div_ubyte(ubyte a1, ubyte a2, ubyte c) {
@ -102,4 +103,12 @@ main {
c64flt.print_f(r)
c64.CHROUT('\n')
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -33,6 +33,7 @@ main {
minus_float(2.5,1.5,1.0)
minus_float(-1.5,3.5,-5.0)
check_eval_stack()
}
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
@ -110,4 +111,13 @@ main {
c64flt.print_f(r)
c64.CHROUT('\n')
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -27,6 +27,7 @@ main {
mul_float(2.5,10,25)
mul_float(-1.5,10,-15)
check_eval_stack()
}
sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) {
@ -104,4 +105,12 @@ main {
c64flt.print_f(r)
c64.CHROUT('\n')
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -31,6 +31,7 @@ main {
plus_float(-1.5,3.5,2.0)
plus_float(-1.1,3.3,2.2)
check_eval_stack()
}
sub plus_ubyte(ubyte a1, ubyte a2, ubyte c) {
@ -108,4 +109,13 @@ main {
c64flt.print_f(r)
c64.CHROUT('\n')
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -5,7 +5,6 @@
main {
sub start() {
c64scr.plot(0,24)
@ -64,6 +63,7 @@ main {
warr[1]--
flarr[1] --
check_ub(ub, 200)
Y=100
Y--
check_ub(Y, 99)
@ -77,7 +77,7 @@ main {
check_uw(uwarr[1], 2000)
check_w(warr[1], -1000)
@($0400+400-1) = X
check_eval_stack()
}
sub check_ub(ubyte value, ubyte expected) {
@ -139,4 +139,13 @@ main {
c64flt.print_f(expected)
c64.CHROUT('\n')
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -15,6 +15,8 @@ main {
remainder_uword(40000,511,142)
remainder_uword(40000,500,0)
remainder_uword(43211,12,11)
check_eval_stack()
}
sub remainder_ubyte(ubyte a1, ubyte a2, ubyte c) {
@ -46,4 +48,12 @@ main {
c64scr.print_uw(r)
c64.CHROUT('\n')
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

130
examples/arithmetic/sgn.p8 Normal file
View File

@ -0,0 +1,130 @@
%import c64flt
%zeropage basicsafe
main {
sub start() {
byte b1
byte b2
ubyte ub1
ubyte ub2
word w1
word w2
uword uw1
uword uw2
float f1
float f2
b1 = 10
b2 = 10
if sgn(b2-b1) != 0
c64scr.print("sgn1 error1\n")
b1 = -100
b2 = -100
if sgn(b2-b1) != 0
c64scr.print("sgn1 error2\n")
ub1 = 200
ub2 = 200
if sgn(ub2-ub1) != 0
c64scr.print("sgn1 error3\n")
w1 = 100
w2 = 100
if sgn(w2-w1) != 0
c64scr.print("sgn1 error4\n")
w1 = -2000
w2 = -2000
if sgn(w2-w1) != 0
c64scr.print("sgn1 error5\n")
uw1 = 999
uw2 = 999
if sgn(uw2-uw1) != 0
c64scr.print("sgn1 error6\n")
f1 = 3.45
f2 = 3.45
if sgn(f2-f1) != 0
c64scr.print("sgn1 error7\n")
; -1
b1 = 11
b2 = 10
if sgn(b2-b1) != -1
c64scr.print("sgn2 error1\n")
b1 = -10
b2 = -100
if sgn(b2-b1) != -1
c64scr.print("sgn2 error2\n")
ub1 = 202
ub2 = 200
if sgn(ub2 as byte - ub1 as byte) != -1
c64scr.print("sgn2 error3\n")
w1 = 101
w2 = 100
if sgn(w2-w1) != -1
c64scr.print("sgn2 error4\n")
w1 = -200
w2 = -2000
if sgn(w2-w1) != -1
c64scr.print("sgn2 error5\n")
uw1 = 2222
uw2 = 999
if sgn((uw2 as word) - (uw1 as word)) != -1
c64scr.print("sgn2 error6a\n")
if sgn(uw2 - uw1) != 1 ; always 0 or 1 if unsigned
c64scr.print("sgn2 error6b\n")
f1 = 3.45
f2 = 1.11
if sgn(f2-f1) != -1
c64scr.print("sgn2 error7\n")
; +1
b1 = 11
b2 = 20
if sgn(b2-b1) != 1
c64scr.print("sgn3 error1\n")
b1 = -10
b2 = -1
if sgn(b2-b1) != 1
c64scr.print("sgn3 error2\n")
ub1 = 202
ub2 = 205
if sgn(ub2-ub1) != 1
c64scr.print("sgn3 error3\n")
w1 = 101
w2 = 200
if sgn(w2-w1) != 1
c64scr.print("sgn3 error4\n")
w1 = -200
w2 = -20
if sgn(w2-w1) != 1
c64scr.print("sgn3 error5\n")
uw1 = 2222
uw2 = 9999
if sgn(uw2-uw1) != 1
c64scr.print("sgn3 error6\n")
f1 = 3.45
f2 = 5.11
if sgn(f2-f1) != 1
c64scr.print("sgn3 error7\n")
c64scr.print("should see no sgn errors\n")
}
}

126
examples/balloonflight.p8 Normal file
View 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 = 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
}
}

View File

@ -16,7 +16,7 @@ sub start() {
void c64.CHRIN()
c64.CLEARSCR()
while(true) {
forever {
uword note
for note in notes {
ubyte note1 = lsb(note)
@ -34,27 +34,28 @@ sub start() {
delay()
}
}
}
sub delay() {
ubyte d
for d in 0 to 12 {
while(c64.RASTER!=0) {
; tempo delay synced to screen refresh
}
sub delay() {
ubyte d
for d in 0 to 12 {
while c64.RASTER!=0 {
; tempo delay synced to screen refresh
}
}
sub print_notes(ubyte n1, ubyte n2) {
c64.CHROUT('\n')
c64scr.plot(n1/2, 24)
c64.COLOR=7
c64.CHROUT('Q')
c64scr.plot(n2/2, 24)
c64.COLOR=4
c64.CHROUT('Q')
}
}
sub print_notes(ubyte n1, ubyte n2) {
c64.CHROUT('\n')
c64scr.plot(n1/2, 24)
c64.COLOR=7
c64.CHROUT('Q')
c64scr.plot(n2/2, 24)
c64.COLOR=4
c64.CHROUT('Q')
}
; details about the boulderdash music can be found here:
; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune

250
examples/c64graphics.p8 Normal file
View File

@ -0,0 +1,250 @@
%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
word dx
word dy
byte ix = 1
byte iy = 1
if x2>x1 {
dx = x2-x1
} else {
ix = -1
dx = x1-x2
}
if y2>y1 {
dy = y2-y1
} else {
iy = -1
dy = y1-y2
}
word dx2 = 2 * dx
word dy2 = 2 * dy
word d = 0
plotx = x1
if dx >= dy {
if ix<0 {
forever {
graphics.plot(y1)
if plotx==x2
return
plotx--
d += dy2
if d > dx {
y1 += iy
d -= dx2
}
}
} else {
forever {
graphics.plot(y1)
if plotx==x2
return
plotx++
d += dy2
if d > dx {
y1 += iy
d -= dx2
}
}
}
} else {
if iy<0 {
forever {
plot(y1)
if y1 == y2
return
y1--
d += dx2
if d > dy {
plotx += ix as word
d -= dy2
}
}
} else {
forever {
plot(y1)
if y1 == y2
return
y1++
d += dx2
if d > dy {
plotx += ix as word
d -= dy2
}
}
}
}
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm
ubyte ploty
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx
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
while xx>=yy {
for plotx in cx to cx+xx {
plot(cy + yy)
plot(cy - yy)
}
for plotx in cx-xx to cx-1 {
plot(cy + yy)
plot(cy - yy)
}
for plotx in cx to cx+yy {
plot(cy + xx)
plot(cy - xx)
}
for plotx in cx-yy to cx {
plot(cy + xx)
plot(cy - 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
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 encode 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
}}
}
}

View File

@ -106,12 +106,15 @@ main {
else
c64scr.print("error in 22>=22!\n")
ubyte endX = X
if endX == 255
c64scr.print("stack x ok!\n")
else
c64scr.print("error: stack x != 255 !\n")
check_eval_stack()
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -106,11 +106,15 @@ main {
else
c64scr.print("error in -22.2>=-22.2!\n")
ubyte endX = X
if endX == 255
c64scr.print("stack x ok!\n")
else
c64scr.print("error: stack x != 255 !\n")
check_eval_stack()
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -106,12 +106,15 @@ main {
else
c64scr.print("error in 22>=22!\n")
ubyte endX = X
if endX == 255
c64scr.print("stack x ok!\n")
else
c64scr.print("error: stack x != 255 !\n")
check_eval_stack()
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -106,13 +106,15 @@ main {
else
c64scr.print("error in 322>=322!\n")
check_eval_stack()
}
ubyte endX = X
if endX == 255
c64scr.print("stack x ok!\n")
else
c64scr.print("error: stack x != 255 !\n")
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -138,13 +138,15 @@ main {
else
c64scr.print("error in 1000>=1000!\n")
check_eval_stack()
}
ubyte endX = X
if endX == 255
c64scr.print("stack x ok!\n")
else
c64scr.print("error: stack x != 255 !\n")
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -52,16 +52,10 @@ main {
c64scr.print("v1=20, v2=-111\n")
compare()
ubyte endX = X
if endX == 255
c64scr.print("\nstack x ok!\n")
else
c64scr.print("\nerror: stack x != 255 !\n")
check_eval_stack()
return
sub compare() {
sub compare() {
c64scr.print(" == != < > <= >=\n")
if v1==v2
@ -98,4 +92,12 @@ main {
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -68,16 +68,10 @@ main {
c64scr.print("v1 = v2 = 0\n")
compare()
ubyte endX = X
if endX == 255
c64scr.print("\nstack x ok!\n")
else
c64scr.print("\nerror: stack x != 255 !\n")
check_eval_stack()
return
sub compare() {
sub compare() {
c64scr.print(" == != < > <= >=\n")
if v1==v2
@ -112,7 +106,13 @@ main {
}
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -52,15 +52,10 @@ main {
c64scr.print("v1=220, v2=10\n")
compare()
ubyte endX = X
if endX == 255
c64scr.print("\nstack x ok!\n")
else
c64scr.print("\nerror: stack x != 255 !\n")
check_eval_stack()
return
sub compare() {
sub compare() {
c64scr.print(" == != < > <= >=\n")
if v1==v2
@ -97,4 +92,12 @@ main {
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -82,16 +82,10 @@ main {
c64scr.print("v1 = v2 = aa\n")
compare()
ubyte endX = X
if endX == 255
c64scr.print("\nstack x ok!\n")
else
c64scr.print("\nerror: stack x != 255 !\n")
check_eval_stack()
return
sub compare() {
sub compare() {
c64scr.print(" == != < > <= >=\n")
if v1==v2
@ -128,4 +122,12 @@ main {
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -118,15 +118,10 @@ main {
c64scr.print("v1 = v2 = aa\n")
compare()
ubyte endX = X
if endX == 255
c64scr.print("\nstack x ok!\n")
else
c64scr.print("\nerror: stack x != 255 !\n")
check_eval_stack()
return
sub compare() {
sub compare() {
c64scr.print(" == != < > <= >=\n")
if v1==v2
@ -163,4 +158,12 @@ main {
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

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