mirror of
https://github.com/irmen/prog8.git
synced 2025-06-15 18:23:35 +00:00
Compare commits
103 Commits
Author | SHA1 | Date | |
---|---|---|---|
22031f39b0 | |||
c4673d3a67 | |||
e83e021541 | |||
c1f2ecd413 | |||
46fbe01df9 | |||
8647a8290e | |||
bac51f4b31 | |||
582aab180a | |||
5fb714fcb2 | |||
3994de77d0 | |||
24c8d1f1f4 | |||
110f877dcc | |||
9cd3a9f8e8 | |||
1464050bf5 | |||
95e9e1b550 | |||
bda1c1c1eb | |||
d020a7974a | |||
a51fad3aab | |||
3cd32778bb | |||
8d67056f84 | |||
e986973b5e | |||
448c934cba | |||
96ef7ba55d | |||
4372de1e7e | |||
af0fb88adf | |||
066233eee8 | |||
b6f85d10b0 | |||
6f75413c09 | |||
d45fe4ce74 | |||
e828c013e6 | |||
988459f744 | |||
7c701bdf3f | |||
446fc35d5c | |||
bec9cc7047 | |||
961380acb6 | |||
84c0685a60 | |||
629222f103 | |||
8c448e5bc2 | |||
b5fa6c2d0a | |||
680b2df08a | |||
09bd47f98b | |||
7f69f9ce4f | |||
4179b4e543 | |||
66364554c4 | |||
43f2448789 | |||
130cee1e70 | |||
b976360248 | |||
225bfc4164 | |||
d7ceda4d82 | |||
14d091e60a | |||
2809668ef4 | |||
bafb86e00b | |||
f5db31b8ff | |||
e1d0dbed0c | |||
1d1fe364d0 | |||
2b9316c4ff | |||
c50cbbb526 | |||
b93d9ecd7e | |||
96243db88b | |||
4daf75a8cc | |||
8c63d7cf5b | |||
6f78a32e64 | |||
af6731c9c8 | |||
25cf0d2b94 | |||
9389791d91 | |||
aa8191d0a1 | |||
0d5c78e875 | |||
e8679ae03b | |||
d1d224b7fc | |||
df995f7bc9 | |||
af39502450 | |||
ffa38955d6 | |||
8d82fb6d8f | |||
306770331a | |||
d3f433c8cf | |||
cf49cbd1f8 | |||
8a99e75299 | |||
2dbf849c82 | |||
ba3dce0b4c | |||
ca9588380a | |||
ae2619602d | |||
de06353194 | |||
3ff3f5e1cc | |||
4b747859b3 | |||
2201765366 | |||
dfa1d5e398 | |||
ce9a90f626 | |||
2deb18beb2 | |||
0f7454059c | |||
f9ba09ac4d | |||
4e74873eae | |||
f0cd03d14f | |||
f2b069c562 | |||
bc89306dc1 | |||
bf4da1655b | |||
d819aa270f | |||
e6d945f835 | |||
4fe408f1fd | |||
c376e42092 | |||
63a653cdf0 | |||
5d900800f2 | |||
def06dbc0b | |||
9b66a597bb |
34
README.md
34
README.md
@ -50,7 +50,7 @@ What use Prog8 provide?
|
||||
|
||||
- "c64": Commodore-64 (6510 CPU = almost a 6502) premium support.
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) experimental support.
|
||||
|
||||
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
|
||||
|
||||
|
||||
@ -75,16 +75,18 @@ Example code
|
||||
|
||||
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
%import c64textio
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
|
||||
|
||||
ubyte[256] sieve
|
||||
ubyte candidate_prime = 2
|
||||
|
||||
ubyte candidate_prime = 2 ; is increased in the loop
|
||||
|
||||
sub start() {
|
||||
memset(sieve, 256, false) ; clear the sieve
|
||||
; clear the sieve, to reset starting situation on subsequent runs
|
||||
memset(sieve, 256, false)
|
||||
; calculate primes
|
||||
txt.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
repeat {
|
||||
@ -95,22 +97,23 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
txt.print(", ")
|
||||
amount++
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
txt.chrout('\n')
|
||||
txt.print("number of primes (expected 54): ")
|
||||
txt.print_ub(amount)
|
||||
c64.CHROUT('\n')
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
|
||||
sub find_next_prime() -> ubyte {
|
||||
while sieve[candidate_prime] {
|
||||
candidate_prime++
|
||||
if candidate_prime==0
|
||||
return 0 ; we wrapped; no more primes available
|
||||
return 0 ; we wrapped; no more primes available in the sieve
|
||||
}
|
||||
|
||||
; found next one, mark the multiples and return it.
|
||||
sieve[candidate_prime] = true
|
||||
uword multiple = candidate_prime
|
||||
|
||||
|
||||
while multiple < len(sieve) {
|
||||
sieve[lsb(multiple)] = true
|
||||
multiple += candidate_prime
|
||||
@ -120,11 +123,11 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
}
|
||||
|
||||
|
||||
|
||||
when compiled an ran on a C-64 you'll get:
|
||||
|
||||

|
||||
|
||||
|
||||
One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this:
|
||||
|
||||

|
||||
@ -136,3 +139,8 @@ Another example (cube3d-sprites.p8) draws the vertices of a rotating 3d cube:
|
||||
If you want to play a video game, a fully working Tetris clone is included in the examples:
|
||||
|
||||

|
||||
|
||||
The CommanderX16 compiler target is quite capable already too, here's a well known space ship
|
||||
animated in 3D with hidden line removal, in the CommanderX16 emulator:
|
||||
|
||||

|
||||
|
@ -355,19 +355,19 @@ mul_f .proc
|
||||
.pend
|
||||
|
||||
neg_f .proc
|
||||
; -- push -flt back on stack
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr NEGOP
|
||||
jmp push_fac1_as_result
|
||||
; -- toggle the sign bit on the stack
|
||||
lda P8ESTACK_HI+3,x
|
||||
eor #$80
|
||||
sta P8ESTACK_HI+3,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
abs_f .proc
|
||||
; -- push abs(float) on stack (as float)
|
||||
jsr pop_float_fac1
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr ABS
|
||||
jmp push_fac1_as_result
|
||||
; -- strip the sign bit on the stack
|
||||
lda P8ESTACK_HI+3,x
|
||||
and #$7f
|
||||
sta P8ESTACK_HI+3,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
equal_f .proc
|
@ -7,7 +7,7 @@
|
||||
%target c64
|
||||
%option enable_floats
|
||||
|
||||
c64flt {
|
||||
floats {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
@ -50,22 +50,22 @@ romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
|
||||
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
||||
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
|
||||
|
||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
|
||||
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
|
||||
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||
|
||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||
; there is also c64flt.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
|
||||
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
|
||||
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
|
||||
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
|
||||
@ -207,11 +207,11 @@ sub print_f (float value) {
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
ldx floats_store_reg
|
||||
+ rts
|
||||
+ ldx floats_store_reg
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64floats.asm", ""
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
|
||||
}
|
@ -1,12 +1,14 @@
|
||||
%target c64
|
||||
%import c64textio
|
||||
%import textio
|
||||
|
||||
; bitmap pixel graphics module for the C64
|
||||
; only black/white monchrome for now
|
||||
; only black/white monchrome 320x200 for now
|
||||
; assumes bitmap screen memory is $2000-$3fff
|
||||
|
||||
graphics {
|
||||
const uword bitmap_address = $2000
|
||||
const uword BITMAP_ADDRESS = $2000
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 200
|
||||
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
@ -16,7 +18,7 @@ graphics {
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||
memset(bitmap_address, 320*200/8, 0)
|
||||
memset(BITMAP_ADDRESS, 320*200/8, 0)
|
||||
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
|
||||
}
|
||||
|
||||
@ -31,8 +33,8 @@ graphics {
|
||||
}
|
||||
word @zp d = 0
|
||||
ubyte positive_ix = true
|
||||
word @zp dx = x2 - x1 as word
|
||||
word @zp dy = y2 as word - y1 as word
|
||||
word @zp dx = x2-x1
|
||||
word @zp dy = y2-y1
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
@ -135,33 +137,33 @@ graphics {
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword cx, ubyte cy, ubyte radius) {
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Midpoint algorithm, filled
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
ubyte cy_plus_yy = cy + yy
|
||||
ubyte cy_min_yy = cy - yy
|
||||
ubyte cy_plus_xx = cy + xx
|
||||
ubyte cy_min_xx = cy - xx
|
||||
ubyte ycenter_plus_yy = ycenter + yy
|
||||
ubyte ycenter_min_yy = ycenter - yy
|
||||
ubyte ycenter_plus_xx = ycenter + xx
|
||||
ubyte ycenter_min_xx = ycenter - xx
|
||||
|
||||
for internal_plotx in cx to cx+xx {
|
||||
internal_plot(cy_plus_yy)
|
||||
internal_plot(cy_min_yy)
|
||||
for internal_plotx in xcenter to xcenter+xx {
|
||||
internal_plot(ycenter_plus_yy)
|
||||
internal_plot(ycenter_min_yy)
|
||||
}
|
||||
for internal_plotx in cx-xx to cx-1 {
|
||||
internal_plot(cy_plus_yy)
|
||||
internal_plot(cy_min_yy)
|
||||
for internal_plotx in xcenter-xx to xcenter-1 {
|
||||
internal_plot(ycenter_plus_yy)
|
||||
internal_plot(ycenter_min_yy)
|
||||
}
|
||||
for internal_plotx in cx to cx+yy {
|
||||
internal_plot(cy_plus_xx)
|
||||
internal_plot(cy_min_xx)
|
||||
for internal_plotx in xcenter to xcenter+yy {
|
||||
internal_plot(ycenter_plus_xx)
|
||||
internal_plot(ycenter_min_xx)
|
||||
}
|
||||
for internal_plotx in cx-yy to cx {
|
||||
internal_plot(cy_plus_xx)
|
||||
internal_plot(cy_min_xx)
|
||||
for internal_plotx in xcenter-yy to xcenter {
|
||||
internal_plot(ycenter_plus_xx)
|
||||
internal_plot(ycenter_min_xx)
|
||||
}
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
@ -177,7 +179,7 @@ graphics {
|
||||
; 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)
|
||||
; uword addr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
|
||||
; @(addr) |= ormask[lsb(px) & 7]
|
||||
; }
|
||||
|
||||
@ -226,7 +228,7 @@ _ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
|
||||
|
||||
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
|
||||
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
|
||||
; the y lookup tables encodes this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
|
||||
|
||||
_plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7)
|
@ -11,6 +11,7 @@ c64 {
|
||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||
&ubyte STATUS = $90 ; kernel status variable for I/O
|
||||
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||
|
||||
@ -250,8 +251,7 @@ asmsub init_system() {
|
||||
sta c64.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
tax
|
||||
tay
|
||||
jsr disable_runstop_and_charsetswitch
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
@ -259,6 +259,26 @@ asmsub init_system() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
lda #14
|
||||
sta $01 ; bank the kernal in
|
||||
jmp (c64.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub disable_runstop_and_charsetswitch() {
|
||||
%asm {{
|
||||
lda #$80
|
||||
sta 657 ; disable charset switching
|
||||
lda #239
|
||||
sta 808 ; disable run/stop key
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irqvec_excl() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
@ -4,23 +4,22 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
%target c64
|
||||
%import c64lib
|
||||
%import syslib
|
||||
%import conv
|
||||
|
||||
|
||||
txt {
|
||||
|
||||
asmsub clear_screen() {
|
||||
%asm {{
|
||||
lda #' '
|
||||
jmp clear_screenchars
|
||||
}}
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
clear_screenchars(' ')
|
||||
}
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte charcolor @ Y) clobbers(A) {
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
; (assumes screen and color matrix are at their default addresses)
|
||||
|
||||
@ -50,7 +49,7 @@ _loop sta c64.Screen,y
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub clear_screencolors (ubyte scrcolor @ A) clobbers(Y) {
|
||||
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||
; ---- clear the character screen colors with the given color (leaves characters).
|
||||
; (assumes color matrix is at the default address)
|
||||
%asm {{
|
||||
@ -69,7 +68,15 @@ sub color (ubyte txtcol) {
|
||||
c64.COLOR = txtcol
|
||||
}
|
||||
|
||||
asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
sub lowercase() {
|
||||
c64.VMCSB |= 2
|
||||
}
|
||||
|
||||
sub uppercase() {
|
||||
c64.VMCSB &= ~2
|
||||
}
|
||||
|
||||
asmsub scroll_left (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
|
||||
@ -108,7 +115,7 @@ _scroll_screen ; scroll the screen memory
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character to the right
|
||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
@ -142,7 +149,7 @@ _scroll_screen ; scroll the screen memory
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character up
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
@ -176,7 +183,7 @@ _scroll_screen ; scroll the screen memory
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character down
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
@ -210,6 +217,8 @@ _scroll_screen ; scroll the screen memory
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
@ -433,21 +442,22 @@ asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setchr (ubyte col @Y, ubyte row @A) clobbers(A) {
|
||||
; ---- set the character in SCRATCH_ZPB1 on the screen matrix at the given position
|
||||
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
|
||||
; ---- sets the character in the screen matrix at the given position
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_REG
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda _screenrows+1,y
|
||||
sta _mod+2
|
||||
lda _screenrows,y
|
||||
txa
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
adc _screenrows,y
|
||||
sta _mod+1
|
||||
bcc +
|
||||
inc _mod+2
|
||||
+ lda P8ZP_SCRATCH_B1
|
||||
+ pla
|
||||
_mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
@ -455,17 +465,18 @@ _screenrows .word $0400 + range(0, 1000, 40)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub getchr (ubyte col @Y, ubyte row @A) clobbers(Y) -> ubyte @ A {
|
||||
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||
; ---- get the character in the screen matrix at the given location
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_B1
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows+1,y
|
||||
sta _mod+2
|
||||
lda setchr._screenrows,y
|
||||
pla
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
adc setchr._screenrows,y
|
||||
sta _mod+1
|
||||
bcc _mod
|
||||
inc _mod+2
|
||||
@ -474,21 +485,22 @@ _mod lda $ffff ; modified
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setclr (ubyte col @Y, ubyte row @A) clobbers(A) {
|
||||
; ---- set the color in SCRATCH_ZPB1 on the screen matrix at the given position
|
||||
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
|
||||
; ---- set the color in A on the screen matrix at the given position
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_REG
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda _colorrows+1,y
|
||||
sta _mod+2
|
||||
lda _colorrows,y
|
||||
txa
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
adc _colorrows,y
|
||||
sta _mod+1
|
||||
bcc +
|
||||
inc _mod+2
|
||||
+ lda P8ZP_SCRATCH_B1
|
||||
+ pla
|
||||
_mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
@ -496,17 +508,18 @@ _colorrows .word $d800 + range(0, 1000, 40)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub getclr (ubyte col @Y, ubyte row @A) clobbers(Y) -> ubyte @ A {
|
||||
asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
|
||||
; ---- get the color in the screen color matrix at the given location
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_B1
|
||||
pha
|
||||
tya
|
||||
asl a
|
||||
tay
|
||||
lda setclr._colorrows+1,y
|
||||
sta _mod+2
|
||||
lda setclr._colorrows,y
|
||||
pla
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
adc setclr._colorrows,y
|
||||
sta _mod+1
|
||||
bcc _mod
|
||||
inc _mod+2
|
||||
@ -553,4 +566,22 @@ asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||
; -- returns the text screen width (number of columns)
|
||||
%asm {{
|
||||
jsr c64.SCREEN
|
||||
txa
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
; -- returns the text screen height (number of rows)
|
||||
%asm {{
|
||||
jsr c64.SCREEN
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
%target cx16
|
||||
%option enable_floats
|
||||
|
||||
c64flt {
|
||||
floats {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
@ -25,7 +25,7 @@ c64flt {
|
||||
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
|
||||
|
||||
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
|
||||
; there is also c64flt.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
|
||||
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
|
||||
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
|
||||
|
||||
@ -143,11 +143,11 @@ sub print_f (float value) {
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
plx
|
||||
+ rts
|
||||
+ plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64floats.asm", ""
|
||||
%asminclude "library:c64/floats.asm", ""
|
||||
|
||||
}
|
156
compiler/res/prog8lib/cx16/graphics.p8
Normal file
156
compiler/res/prog8lib/cx16/graphics.p8
Normal file
@ -0,0 +1,156 @@
|
||||
%target cx16
|
||||
%import syslib
|
||||
|
||||
; bitmap pixel graphics module for the CommanderX16
|
||||
; wraps the graphics functions that are in ROM.
|
||||
; only black/white monchrome 320x200 for now.
|
||||
|
||||
graphics {
|
||||
const uword WIDTH = 320
|
||||
const ubyte HEIGHT = 200
|
||||
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
void cx16.screen_set_mode($80)
|
||||
cx16.r0 = 0
|
||||
cx16.GRAPH_init()
|
||||
clear_screen(1, 0)
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||
cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor)
|
||||
cx16.GRAPH_clear()
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
cx16.r0 = x1
|
||||
cx16.r1 = y1
|
||||
cx16.r2 = x2
|
||||
cx16.r3 = y2
|
||||
cx16.GRAPH_draw_line()
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
;cx16.r0 = xcenter - radius/2
|
||||
;cx16.r1 = ycenter - radius/2
|
||||
;cx16.r2 = radius*2
|
||||
;cx16.r3 = radius*2
|
||||
;cx16.GRAPH_draw_oval(false) ; TODO currently is not implemented on cx16, does a BRK
|
||||
|
||||
; Midpoint algorithm
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
byte @zp decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter + yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + xx
|
||||
cx16.r1 = ycenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter + xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter + yy
|
||||
cx16.r1 = ycenter - xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r0 = xcenter - yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
yy++
|
||||
if decisionOver2<=0 {
|
||||
decisionOver2 += 2*yy+1
|
||||
} else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; cx16.r0 = xcenter - radius/2
|
||||
; cx16.r1 = ycenter - radius/2
|
||||
; cx16.r2 = radius*2
|
||||
; cx16.r3 = radius*2
|
||||
; cx16.GRAPH_draw_oval(true) ; TODO currently is not implemented on cx16, does a BRK
|
||||
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
ubyte ycenter_plus_yy = ycenter + yy
|
||||
ubyte ycenter_min_yy = ycenter - yy
|
||||
ubyte ycenter_plus_xx = ycenter + xx
|
||||
ubyte ycenter_min_xx = ycenter - xx
|
||||
uword @zp plotx
|
||||
|
||||
for plotx in xcenter to xcenter+xx {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter-xx to xcenter-1 {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_yy
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter to xcenter+yy {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
for plotx in xcenter-yy to xcenter {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ycenter_plus_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
cx16.r1 = ycenter_min_xx
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub plot(uword plotx, ubyte ploty) {
|
||||
cx16.r0 = plotx
|
||||
cx16.r1 = ploty
|
||||
cx16.FB_cursor_position()
|
||||
cx16.FB_set_pixel(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; r
|
||||
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
|
||||
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
|
||||
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
|
||||
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> ubyte @A, uword @ XY ; read/set top of memory pointer, returns number of banks in A
|
||||
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
|
||||
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
|
||||
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
|
||||
@ -60,69 +60,69 @@ romsub $FFF3 = IOBASE() -> uword @ XY ; read base addr
|
||||
|
||||
cx16 {
|
||||
|
||||
; 65c02 hardware vectors:
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
; 65c02 hardware vectors:
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
|
||||
; the sixteen virtual 16-bit registers
|
||||
&uword r0 = $02
|
||||
&uword r1 = $04
|
||||
&uword r2 = $06
|
||||
&uword r3 = $08
|
||||
&uword r4 = $0a
|
||||
&uword r5 = $0c
|
||||
&uword r6 = $0e
|
||||
&uword r7 = $10
|
||||
&uword r8 = $12
|
||||
&uword r9 = $14
|
||||
&uword r10 = $16
|
||||
&uword r11 = $18
|
||||
&uword r12 = $1a
|
||||
&uword r13 = $1c
|
||||
&uword r14 = $1e
|
||||
&uword r15 = $20
|
||||
&uword r0 = $0002
|
||||
&uword r1 = $0004
|
||||
&uword r2 = $0006
|
||||
&uword r3 = $0008
|
||||
&uword r4 = $000a
|
||||
&uword r5 = $000c
|
||||
&uword r6 = $000e
|
||||
&uword r7 = $0010
|
||||
&uword r8 = $0012
|
||||
&uword r9 = $0014
|
||||
&uword r10 = $0016
|
||||
&uword r11 = $0018
|
||||
&uword r12 = $001a
|
||||
&uword r13 = $001c
|
||||
&uword r14 = $001e
|
||||
&uword r15 = $0020
|
||||
|
||||
; VERA registers
|
||||
|
||||
const uword VERA_BASE = $9F20
|
||||
&ubyte VERA_ADDR_L = VERA_BASE + $00
|
||||
&ubyte VERA_ADDR_M = VERA_BASE + $01
|
||||
&ubyte VERA_ADDR_H = VERA_BASE + $02
|
||||
&ubyte VERA_DATA0 = VERA_BASE + $03
|
||||
&ubyte VERA_DATA1 = VERA_BASE + $04
|
||||
&ubyte VERA_CTRL = VERA_BASE + $05
|
||||
&ubyte VERA_IEN = VERA_BASE + $06
|
||||
&ubyte VERA_ISR = VERA_BASE + $07
|
||||
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $08
|
||||
&ubyte VERA_DC_VIDEO = VERA_BASE + $09
|
||||
&ubyte VERA_DC_HSCALE = VERA_BASE + $0A
|
||||
&ubyte VERA_DC_VSCALE = VERA_BASE + $0B
|
||||
&ubyte VERA_DC_BORDER = VERA_BASE + $0C
|
||||
&ubyte VERA_DC_HSTART = VERA_BASE + $09
|
||||
&ubyte VERA_DC_HSTOP = VERA_BASE + $0A
|
||||
&ubyte VERA_DC_VSTART = VERA_BASE + $0B
|
||||
&ubyte VERA_DC_VSTOP = VERA_BASE + $0C
|
||||
&ubyte VERA_L0_CONFIG = VERA_BASE + $0D
|
||||
&ubyte VERA_L0_MAPBASE = VERA_BASE + $0E
|
||||
&ubyte VERA_L0_TILEBASE = VERA_BASE + $0F
|
||||
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $10
|
||||
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $11
|
||||
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $12
|
||||
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $13
|
||||
&ubyte VERA_L1_CONFIG = VERA_BASE + $14
|
||||
&ubyte VERA_L1_MAPBASE = VERA_BASE + $15
|
||||
&ubyte VERA_L1_TILEBASE = VERA_BASE + $16
|
||||
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $17
|
||||
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $18
|
||||
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $19
|
||||
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $1A
|
||||
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $1B
|
||||
&ubyte VERA_AUDIO_RATE = VERA_BASE + $1C
|
||||
&ubyte VERA_AUDIO_DATA = VERA_BASE + $1D
|
||||
&ubyte VERA_SPI_DATA = VERA_BASE + $1E
|
||||
&ubyte VERA_SPI_CTRL = VERA_BASE + $1F
|
||||
&ubyte VERA_ADDR_L = VERA_BASE + $0000
|
||||
&ubyte VERA_ADDR_M = VERA_BASE + $0001
|
||||
&ubyte VERA_ADDR_H = VERA_BASE + $0002
|
||||
&ubyte VERA_DATA0 = VERA_BASE + $0003
|
||||
&ubyte VERA_DATA1 = VERA_BASE + $0004
|
||||
&ubyte VERA_CTRL = VERA_BASE + $0005
|
||||
&ubyte VERA_IEN = VERA_BASE + $0006
|
||||
&ubyte VERA_ISR = VERA_BASE + $0007
|
||||
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
|
||||
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
|
||||
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
|
||||
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
|
||||
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
|
||||
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
|
||||
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
|
||||
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
|
||||
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
|
||||
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
|
||||
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
|
||||
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
|
||||
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
|
||||
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
|
||||
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
|
||||
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
|
||||
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014
|
||||
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
|
||||
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
|
||||
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
|
||||
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
|
||||
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
|
||||
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
|
||||
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
|
||||
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
|
||||
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
|
||||
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
|
||||
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
|
||||
; VERA_PSG_BASE = $1F9C0
|
||||
; VERA_PALETTE_BASE = $1FA00
|
||||
; VERA_SPRITES_BASE = $1FC00
|
||||
@ -169,20 +169,18 @@ cx16 {
|
||||
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||
; spelling of the names is taken from the Commander X-16 rom sources
|
||||
|
||||
; TODO specify the correct clobbers for alle these functions, for simplicity all 3 regs are marked clobbered atm
|
||||
|
||||
; supported C128 additions
|
||||
romsub $ff4a = close_all() clobbers(A,X,Y)
|
||||
romsub $ff59 = lkupla() clobbers(A,X,Y)
|
||||
romsub $ff5c = lkupsa() clobbers(A,X,Y)
|
||||
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
|
||||
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
|
||||
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
|
||||
romsub $ff5f = screen_set_mode(ubyte mode @A) clobbers(A, X, Y) -> ubyte @Pc
|
||||
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
|
||||
romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||
romsub $ff6e = jsrfar() clobbers(A,X,Y)
|
||||
romsub $ff74 = fetch() clobbers(A,X,Y)
|
||||
romsub $ff77 = stash() clobbers(A,X,Y)
|
||||
romsub $ff7a = cmpare() clobbers(A,X,Y)
|
||||
romsub $ff7d = primm() clobbers(A,X,Y)
|
||||
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
|
||||
romsub $ff6e = jsrfar()
|
||||
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
|
||||
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
|
||||
romsub $ff7d = primm()
|
||||
|
||||
; X16 additions
|
||||
romsub $ff44 = macptr() clobbers(A,X,Y)
|
||||
@ -193,7 +191,9 @@ romsub $ff71 = mouse_scan() clobbers(A, X, Y)
|
||||
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
|
||||
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $ff4d = clock_set_date_time() clobbers(A, X, Y) ; args: r0, r1, r2, r3L
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A) ; outout args: r0, r1, r2, r3L
|
||||
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) ; outout args: r0, r1, r2, r3L
|
||||
|
||||
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
|
||||
|
||||
; high level graphics & fonts
|
||||
romsub $ff20 = GRAPH_init() clobbers(A,X,Y) ; uses vectors=r0
|
||||
@ -259,6 +259,14 @@ asmsub init_system() {
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
lda #$90 ; black
|
||||
jsr c64.CHROUT
|
||||
lda #1 ; swap fg/bg
|
||||
jsr c64.CHROUT
|
||||
lda #$9e ; yellow
|
||||
jsr c64.CHROUT
|
||||
lda #147 ; clear screen
|
||||
jsr c64.CHROUT
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
@ -269,4 +277,15 @@ asmsub init_system() {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub reset_system() {
|
||||
; Soft-reset the system back to Basic prompt.
|
||||
%asm {{
|
||||
sei
|
||||
lda #14
|
||||
sta $01
|
||||
stz cx16.d1prb ; bank the kernal in
|
||||
jmp (cx16.RESET_VEC)
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
696
compiler/res/prog8lib/cx16/textio.p8
Normal file
696
compiler/res/prog8lib/cx16/textio.p8
Normal file
@ -0,0 +1,696 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target cx16
|
||||
%import syslib
|
||||
%import conv
|
||||
|
||||
|
||||
txt {
|
||||
|
||||
const ubyte DEFAULT_WIDTH = 80
|
||||
const ubyte DEFAULT_HEIGHT = 60
|
||||
|
||||
|
||||
sub clear_screen() {
|
||||
clear_screenchars(' ')
|
||||
}
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
%asm {{
|
||||
sty _ly+1
|
||||
phx
|
||||
pha
|
||||
jsr c64.SCREEN ; get dimensions in X/Y
|
||||
txa
|
||||
lsr a
|
||||
lsr a
|
||||
sta _lx+1
|
||||
stz cx16.VERA_CTRL
|
||||
lda #%00010000
|
||||
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||
stz cx16.VERA_ADDR_L ; start at (0,0)
|
||||
stz cx16.VERA_ADDR_M
|
||||
pla
|
||||
_lx ldx #0 ; modified
|
||||
phy
|
||||
_ly ldy #1 ; modified
|
||||
- sta cx16.VERA_DATA0
|
||||
sty cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sty cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sty cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sty cx16.VERA_DATA0
|
||||
dex
|
||||
bne -
|
||||
ply
|
||||
dey
|
||||
beq +
|
||||
stz cx16.VERA_ADDR_L
|
||||
inc cx16.VERA_ADDR_M ; next line
|
||||
bra _lx
|
||||
+ plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
; ---- clear the character screen with the given fill character (leaves colors)
|
||||
; (assumes screen matrix is at the default address)
|
||||
%asm {{
|
||||
phx
|
||||
pha
|
||||
jsr c64.SCREEN ; get dimensions in X/Y
|
||||
txa
|
||||
lsr a
|
||||
lsr a
|
||||
sta _lx+1
|
||||
stz cx16.VERA_CTRL
|
||||
lda #%00100000
|
||||
sta cx16.VERA_ADDR_H ; enable auto increment by 2, bank 0.
|
||||
stz cx16.VERA_ADDR_L ; start at (0,0)
|
||||
stz cx16.VERA_ADDR_M
|
||||
pla
|
||||
_lx ldx #0 ; modified
|
||||
- sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
dex
|
||||
bne -
|
||||
dey
|
||||
beq +
|
||||
stz cx16.VERA_ADDR_L
|
||||
inc cx16.VERA_ADDR_M ; next line
|
||||
bra _lx
|
||||
+ plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
|
||||
; ---- clear the character screen colors with the given color (leaves characters).
|
||||
; (assumes color matrix is at the default address)
|
||||
%asm {{
|
||||
phx
|
||||
sta _la+1
|
||||
jsr c64.SCREEN ; get dimensions in X/Y
|
||||
txa
|
||||
lsr a
|
||||
lsr a
|
||||
sta _lx+1
|
||||
stz cx16.VERA_CTRL
|
||||
lda #%00100000
|
||||
sta cx16.VERA_ADDR_H ; enable auto increment by 2, bank 0.
|
||||
lda #1
|
||||
sta cx16.VERA_ADDR_L ; start at (1,0)
|
||||
stz cx16.VERA_ADDR_M
|
||||
_lx ldx #0 ; modified
|
||||
_la lda #0 ; modified
|
||||
- sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA0
|
||||
dex
|
||||
bne -
|
||||
dey
|
||||
beq +
|
||||
lda #1
|
||||
sta cx16.VERA_ADDR_L
|
||||
inc cx16.VERA_ADDR_M ; next line
|
||||
bra _lx
|
||||
+ plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
|
||||
|
||||
sub color (ubyte txtcol) {
|
||||
c64.CHROUT(color_to_charcode[txtcol & 15])
|
||||
}
|
||||
|
||||
sub color2 (ubyte txtcol, ubyte bgcol) {
|
||||
c64.CHROUT(color_to_charcode[bgcol & 15])
|
||||
c64.CHROUT(1) ; switch fg and bg colors
|
||||
c64.CHROUT(color_to_charcode[txtcol & 15])
|
||||
}
|
||||
|
||||
sub lowercase() {
|
||||
cx16.screen_set_charset(3, 0) ; lowercase charset
|
||||
}
|
||||
|
||||
sub uppercase() {
|
||||
cx16.screen_set_charset(2, 0) ; uppercase charset
|
||||
}
|
||||
|
||||
asmsub scroll_left (ubyte dummy @ 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 is a dummy on the cx16
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.SCREEN
|
||||
dex
|
||||
stx _lx+1
|
||||
dey
|
||||
sty P8ZP_SCRATCH_B1 ; number of rows to scroll
|
||||
|
||||
_nextline
|
||||
stz cx16.VERA_CTRL ; data port 0: source column
|
||||
lda #%00010000 ; auto increment 1
|
||||
sta cx16.VERA_ADDR_H
|
||||
lda #2
|
||||
sta cx16.VERA_ADDR_L ; begin in column 1
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
sty cx16.VERA_ADDR_M
|
||||
lda #1
|
||||
sta cx16.VERA_CTRL ; data port 1: destination column
|
||||
lda #%00010000 ; auto increment 1
|
||||
sta cx16.VERA_ADDR_H
|
||||
stz cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
|
||||
_lx ldx #0 ; modified
|
||||
- lda cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA1 ; copy char
|
||||
lda cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA1 ; copy color
|
||||
dex
|
||||
bne -
|
||||
dec P8ZP_SCRATCH_B1
|
||||
bpl _nextline
|
||||
|
||||
lda #0
|
||||
sta cx16.VERA_CTRL
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_right (ubyte dummy @ Pc) clobbers(A) {
|
||||
; ---- scroll the whole screen 1 character to the right
|
||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag is a dummy on the cx16
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.SCREEN
|
||||
dex
|
||||
stx _lx+1
|
||||
txa
|
||||
asl a
|
||||
dea
|
||||
sta _rcol+1
|
||||
ina
|
||||
ina
|
||||
sta _rcol2+1
|
||||
dey
|
||||
sty P8ZP_SCRATCH_B1 ; number of rows to scroll
|
||||
|
||||
_nextline
|
||||
stz cx16.VERA_CTRL ; data port 0: source column
|
||||
lda #%00011000 ; auto decrement 1
|
||||
sta cx16.VERA_ADDR_H
|
||||
_rcol lda #79*2-1 ; modified
|
||||
sta cx16.VERA_ADDR_L ; begin in rightmost column minus one
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
sty cx16.VERA_ADDR_M
|
||||
lda #1
|
||||
sta cx16.VERA_CTRL ; data port 1: destination column
|
||||
lda #%00011000 ; auto decrement 1
|
||||
sta cx16.VERA_ADDR_H
|
||||
_rcol2 lda #79*2+1 ; modified
|
||||
sta cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
|
||||
_lx ldx #0 ; modified
|
||||
- lda cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA1 ; copy char
|
||||
lda cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA1 ; copy color
|
||||
dex
|
||||
bne -
|
||||
dec P8ZP_SCRATCH_B1
|
||||
bpl _nextline
|
||||
|
||||
lda #0
|
||||
sta cx16.VERA_CTRL
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_up (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character up
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag is a dummy on the cx16
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.SCREEN
|
||||
stx _nextline+1
|
||||
dey
|
||||
sty P8ZP_SCRATCH_B1
|
||||
stz cx16.VERA_CTRL ; data port 0 is source
|
||||
lda #1
|
||||
sta cx16.VERA_ADDR_M ; start at second line
|
||||
stz cx16.VERA_ADDR_L
|
||||
lda #%00010000
|
||||
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||
|
||||
lda #1
|
||||
sta cx16.VERA_CTRL ; data port 1 is destination
|
||||
stz cx16.VERA_ADDR_M ; start at top line
|
||||
stz cx16.VERA_ADDR_L
|
||||
lda #%00010000
|
||||
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||
|
||||
_nextline
|
||||
ldx #80 ; modified
|
||||
- lda cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA1 ; copy char
|
||||
lda cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA1 ; copy color
|
||||
dex
|
||||
bne -
|
||||
dec P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
stz cx16.VERA_CTRL ; data port 0
|
||||
stz cx16.VERA_ADDR_L
|
||||
inc cx16.VERA_ADDR_M
|
||||
lda #1
|
||||
sta cx16.VERA_CTRL ; data port 1
|
||||
stz cx16.VERA_ADDR_L
|
||||
inc cx16.VERA_ADDR_M
|
||||
bra _nextline
|
||||
|
||||
+ lda #0
|
||||
sta cx16.VERA_CTRL
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_down (ubyte dummy @ Pc) clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character down
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; Carry flag is a dummy on the cx16
|
||||
%asm {{
|
||||
phx
|
||||
jsr c64.SCREEN
|
||||
stx _nextline+1
|
||||
dey
|
||||
sty P8ZP_SCRATCH_B1
|
||||
stz cx16.VERA_CTRL ; data port 0 is source
|
||||
dey
|
||||
sty cx16.VERA_ADDR_M ; start at line before bottom line
|
||||
stz cx16.VERA_ADDR_L
|
||||
lda #%00010000
|
||||
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||
|
||||
lda #1
|
||||
sta cx16.VERA_CTRL ; data port 1 is destination
|
||||
iny
|
||||
sty cx16.VERA_ADDR_M ; start at bottom line
|
||||
stz cx16.VERA_ADDR_L
|
||||
lda #%00010000
|
||||
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
|
||||
|
||||
_nextline
|
||||
ldx #80 ; modified
|
||||
- lda cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA1 ; copy char
|
||||
lda cx16.VERA_DATA0
|
||||
sta cx16.VERA_DATA1 ; copy color
|
||||
dex
|
||||
bne -
|
||||
dec P8ZP_SCRATCH_B1
|
||||
beq +
|
||||
stz cx16.VERA_CTRL ; data port 0
|
||||
stz cx16.VERA_ADDR_L
|
||||
dec cx16.VERA_ADDR_M
|
||||
lda #1
|
||||
sta cx16.VERA_CTRL ; data port 1
|
||||
stz cx16.VERA_ADDR_L
|
||||
dec cx16.VERA_ADDR_M
|
||||
bra _nextline
|
||||
|
||||
+ lda #0
|
||||
sta cx16.VERA_CTRL
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to c64.CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
phx
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
txa
|
||||
jsr c64.CHROUT
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
jsr conv.ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
jmp _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
jsr c64.CHROUT
|
||||
_ones txa
|
||||
jsr c64.CHROUT
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
phx
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr c64.CHROUT
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
phx
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
jsr c64.CHROUT
|
||||
+ ldy #8
|
||||
- lda #'0'
|
||||
asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
+ jsr c64.CHROUT
|
||||
dey
|
||||
bne -
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubbin
|
||||
pla
|
||||
clc
|
||||
jmp print_ubbin
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubhex
|
||||
pla
|
||||
clc
|
||||
jmp print_ubhex
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
phx
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
jsr conv.uword2decimal
|
||||
plx
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
iny
|
||||
bne -
|
||||
|
||||
_gotdigit
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero
|
||||
lda #'0'
|
||||
jmp c64.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp print_uw
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
|
||||
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
|
||||
; It assumes the keyboard is selected as I/O channel!
|
||||
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0 ; char counter = 0
|
||||
- jsr c64.CHRIN
|
||||
cmp #$0d ; return (ascii 13) pressed?
|
||||
beq + ; yes, end.
|
||||
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
|
||||
iny
|
||||
bne -
|
||||
+ lda #0
|
||||
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
|
||||
rts
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
|
||||
; ---- sets the character in the screen matrix at the given position
|
||||
%asm {{
|
||||
pha
|
||||
txa
|
||||
asl a
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
sta cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
pla
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||
; ---- get the character in the screen matrix at the given location
|
||||
%asm {{
|
||||
asl a
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
sta cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
lda cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
|
||||
; ---- set the color in A on the screen matrix at the given position
|
||||
%asm {{
|
||||
pha
|
||||
txa
|
||||
asl a
|
||||
ina
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
sta cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
pla
|
||||
sta cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
|
||||
; ---- get the color in the screen color matrix at the given location
|
||||
%asm {{
|
||||
asl a
|
||||
ina
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
sta cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
lda cx16.VERA_DATA0
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
; ---- set char+color at the given position on the screen
|
||||
%asm {{
|
||||
phx
|
||||
lda column
|
||||
asl a
|
||||
tax
|
||||
ldy row
|
||||
lda charcolor
|
||||
and #$0f
|
||||
sta P8ZP_SCRATCH_B1
|
||||
stz cx16.VERA_CTRL
|
||||
stz cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
lda char
|
||||
sta cx16.VERA_DATA0
|
||||
inx
|
||||
stz cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
lda cx16.VERA_DATA0
|
||||
and #$f0
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta cx16.VERA_DATA0
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||
; ---- safe wrapper around PLOT kernel routine, to save the X register.
|
||||
%asm {{
|
||||
phx
|
||||
tax
|
||||
clc
|
||||
jsr c64.PLOT
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub width() clobbers(X,Y) -> ubyte @A {
|
||||
; -- returns the text screen width (number of columns)
|
||||
%asm {{
|
||||
jsr c64.SCREEN
|
||||
txa
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub height() clobbers(X, Y) -> ubyte @A {
|
||||
; -- returns the text screen height (number of rows)
|
||||
%asm {{
|
||||
jsr c64.SCREEN
|
||||
tya
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
@ -1,344 +0,0 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%target cx16
|
||||
%import cx16lib
|
||||
%import conv
|
||||
|
||||
|
||||
txt {
|
||||
|
||||
sub clear_screen() {
|
||||
c64.CHROUT(147) ; clear screen (spaces)
|
||||
}
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte txtcolor @ Y) clobbers(A) {
|
||||
; ---- fill the character screen with the given fill character and character color.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W1 ; fillchar
|
||||
sty P8ZP_SCRATCH_W1+1 ; textcolor
|
||||
phx
|
||||
jsr c64.SCREEN ; get dimensions in X/Y
|
||||
dex
|
||||
dey
|
||||
txa
|
||||
asl a
|
||||
adc #1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- ldx P8ZP_SCRATCH_B1
|
||||
- stz cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
lda cx16.VERA_DATA0
|
||||
and #$f0
|
||||
ora P8ZP_SCRATCH_W1+1
|
||||
sta cx16.VERA_DATA0
|
||||
dex
|
||||
stz cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta cx16.VERA_DATA0
|
||||
dex
|
||||
cpx #255
|
||||
bne -
|
||||
dey
|
||||
bpl --
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
|
||||
}
|
||||
|
||||
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
|
||||
; ---- clear the character screen with the given fill character (leaves colors)
|
||||
; (assumes screen matrix is at the default address)
|
||||
%asm {{
|
||||
pha
|
||||
phx
|
||||
jsr c64.SCREEN ; get dimensions in X/Y
|
||||
dex
|
||||
dey
|
||||
txa
|
||||
asl a
|
||||
sta P8ZP_SCRATCH_B1
|
||||
pla
|
||||
- ldx P8ZP_SCRATCH_B1
|
||||
- stz cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
sta cx16.VERA_DATA0
|
||||
dex
|
||||
dex
|
||||
cpx #254
|
||||
bne -
|
||||
dey
|
||||
bpl --
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
|
||||
|
||||
sub color (ubyte txtcol) {
|
||||
c64.CHROUT(color_to_charcode[txtcol & 15])
|
||||
}
|
||||
|
||||
sub color2 (ubyte txtcol, ubyte bgcol) {
|
||||
c64.CHROUT(color_to_charcode[bgcol & 15])
|
||||
c64.CHROUT(1) ; switch fg and bg colors
|
||||
c64.CHROUT(color_to_charcode[txtcol & 15])
|
||||
}
|
||||
|
||||
asmsub print (str text @ AY) clobbers(A,Y) {
|
||||
; ---- print null terminated string from A/Y
|
||||
; note: the compiler contains an optimization that will replace
|
||||
; a call to this subroutine with a string argument of just one char,
|
||||
; by just one call to c64.CHROUT of that single char.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
phx
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
txa
|
||||
jsr c64.CHROUT
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
jsr conv.ubyte2decimal
|
||||
_print_byte_digits
|
||||
pha
|
||||
cpy #'0'
|
||||
beq +
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
jmp _ones
|
||||
+ pla
|
||||
cmp #'0'
|
||||
beq _ones
|
||||
jsr c64.CHROUT
|
||||
_ones txa
|
||||
jsr c64.CHROUT
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jmp print_ub._print_byte_digits
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
phx
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr c64.CHROUT
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
phx
|
||||
sta P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'%'
|
||||
jsr c64.CHROUT
|
||||
+ ldy #8
|
||||
- lda #'0'
|
||||
asl P8ZP_SCRATCH_B1
|
||||
bcc +
|
||||
lda #'1'
|
||||
+ jsr c64.CHROUT
|
||||
dey
|
||||
bne -
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubbin
|
||||
pla
|
||||
clc
|
||||
jmp print_ubbin
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in hexadecimal form (4 digits)
|
||||
; (if Carry is set, a radix prefix '$' is printed as well)
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr print_ubhex
|
||||
pla
|
||||
clc
|
||||
jmp print_ubhex
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
|
||||
%asm {{
|
||||
phx
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
phx
|
||||
jsr conv.uword2decimal
|
||||
plx
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq _allzero
|
||||
cmp #'0'
|
||||
bne _gotdigit
|
||||
iny
|
||||
bne -
|
||||
|
||||
_gotdigit
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
lda conv.uword2decimal.decTenThousands,y
|
||||
bne _gotdigit
|
||||
rts
|
||||
_allzero
|
||||
lda #'0'
|
||||
jmp c64.CHROUT
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_w (word value @ AY) clobbers(A,Y) {
|
||||
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
|
||||
%asm {{
|
||||
cpy #0
|
||||
bpl +
|
||||
pha
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
tya
|
||||
eor #255
|
||||
tay
|
||||
pla
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp print_uw
|
||||
}}
|
||||
}
|
||||
|
||||
; TODO implement the "missing" txtio subroutines
|
||||
|
||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
; ---- set char+color at the given position on the screen
|
||||
%asm {{
|
||||
phx
|
||||
lda column
|
||||
asl a
|
||||
tax
|
||||
ldy row
|
||||
lda charcolor
|
||||
and #$0f
|
||||
sta P8ZP_SCRATCH_B1
|
||||
stz cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
lda char
|
||||
sta cx16.VERA_DATA0
|
||||
inx
|
||||
stz cx16.VERA_ADDR_H
|
||||
stx cx16.VERA_ADDR_L
|
||||
sty cx16.VERA_ADDR_M
|
||||
lda cx16.VERA_DATA0
|
||||
and #$f0
|
||||
ora P8ZP_SCRATCH_B1
|
||||
sta cx16.VERA_DATA0
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||
; ---- safe wrapper around PLOT kernel routine, to save the X register.
|
||||
%asm {{
|
||||
phx
|
||||
tax
|
||||
clc
|
||||
jsr c64.PLOT
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
@ -31,7 +31,7 @@ _enterloop lsr P8ZP_SCRATCH_REG
|
||||
.pend
|
||||
|
||||
|
||||
multiply_bytes_16 .proc
|
||||
multiply_bytes_into_word .proc
|
||||
; -- multiply 2 bytes A and Y, result as word in A/Y (unsigned)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
|
@ -1 +1 @@
|
||||
4.2
|
||||
4.4
|
||||
|
@ -53,12 +53,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(expr: BinaryExpression) {
|
||||
output("(")
|
||||
expr.left.accept(this)
|
||||
if(expr.operator.any { it.isLetter() })
|
||||
output(" ${expr.operator} ")
|
||||
else
|
||||
output(expr.operator)
|
||||
expr.right.accept(this)
|
||||
output(")")
|
||||
}
|
||||
|
||||
override fun visit(directive: Directive) {
|
||||
@ -85,7 +87,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
DataType.ARRAY_W -> "word["
|
||||
DataType.ARRAY_F -> "float["
|
||||
DataType.STRUCT -> "" // the name of the struct is enough
|
||||
else -> "?????2"
|
||||
else -> "?????"
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +115,10 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
VarDeclType.CONST -> output("const ")
|
||||
VarDeclType.MEMORY -> output("&")
|
||||
}
|
||||
output(decl.struct?.name ?: "")
|
||||
|
||||
if(decl.datatype==DataType.STRUCT && decl.struct!=null)
|
||||
output(decl.struct!!.name)
|
||||
|
||||
output(datatypeString(decl.datatype))
|
||||
if(decl.arraysize!=null) {
|
||||
decl.arraysize!!.index.accept(this)
|
||||
@ -338,7 +343,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
output("do ")
|
||||
untilLoop.body.accept(this)
|
||||
output(" until ")
|
||||
untilLoop.untilCondition.accept(this)
|
||||
untilLoop.condition.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(returnStmt: Return) {
|
||||
|
@ -137,7 +137,15 @@ interface INameScope {
|
||||
}
|
||||
return null
|
||||
} else {
|
||||
// unqualified name, find the scope the localContext is in, look in that first
|
||||
// unqualified name
|
||||
// special case: the do....until statement can also look INSIDE the anonymous scope
|
||||
if(localContext.parent.parent is UntilLoop) {
|
||||
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.getLabelOrVariable(scopedName[0])
|
||||
if(symbolFromInnerScope!=null)
|
||||
return symbolFromInnerScope
|
||||
}
|
||||
|
||||
// find the scope the localContext is in, look in that first
|
||||
var statementScope = localContext
|
||||
while(statementScope !is ParentSentinel) {
|
||||
val localScope = statementScope.definingScope()
|
||||
@ -232,7 +240,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
modules.forEach {
|
||||
it.linkParents(this)
|
||||
it.linkParents(namespace)
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,6 +320,14 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// special case: the do....until statement can also look INSIDE the anonymous scope
|
||||
if(localContext.parent.parent is UntilLoop) {
|
||||
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.lookup(scopedName, localContext)
|
||||
if(symbolFromInnerScope!=null)
|
||||
return symbolFromInnerScope
|
||||
}
|
||||
|
||||
// lookup something from the module.
|
||||
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
|
||||
is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt
|
||||
|
@ -30,7 +30,7 @@ private fun ParserRuleContext.toPosition() : Position {
|
||||
val customTokensource = this.start.tokenSource as? CustomLexer
|
||||
val filename =
|
||||
when {
|
||||
customTokensource!=null -> customTokensource.modulePath.fileName.toString()
|
||||
customTokensource!=null -> customTokensource.modulePath.toString()
|
||||
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
|
||||
else -> File(start.inputStream.sourceName).name
|
||||
}
|
||||
@ -397,7 +397,7 @@ private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
|
||||
}
|
||||
|
||||
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
|
||||
fun makeLiteral(text: String, radix: Int): NumericLiteral {
|
||||
val integer: Int
|
||||
var datatype = DataType.UBYTE
|
||||
when (radix) {
|
||||
@ -435,14 +435,14 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
|
||||
}
|
||||
else -> throw FatalAstException("invalid radix")
|
||||
}
|
||||
return NumericLiteral(integer, if (forceWord) DataType.UWORD else datatype)
|
||||
return NumericLiteral(integer, datatype)
|
||||
}
|
||||
val terminal: TerminalNode = children[0] as TerminalNode
|
||||
val integerPart = this.intpart.text
|
||||
return when (terminal.symbol.type) {
|
||||
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null)
|
||||
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null)
|
||||
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null)
|
||||
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10)
|
||||
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
|
||||
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
|
||||
else -> throw FatalAstException(terminal.text)
|
||||
}
|
||||
}
|
||||
|
@ -165,6 +165,7 @@ object ParentSentinel : 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}]"
|
||||
fun toClickableStr(): String = "$file:$line:$startCol:"
|
||||
|
||||
companion object {
|
||||
val DUMMY = Position("<dummy>", 0, 0, 0)
|
||||
|
@ -24,7 +24,7 @@ class ErrorReporter {
|
||||
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()
|
||||
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
|
||||
if(msg !in alreadyReportedMessages) {
|
||||
System.err.println(msg)
|
||||
alreadyReportedMessages.add(msg)
|
||||
|
@ -7,11 +7,11 @@ open class FatalAstException (override var message: String) : Exception(message)
|
||||
open class AstException (override var message: String) : Exception(message)
|
||||
|
||||
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
|
||||
override fun toString() = "$position Syntax error: $message"
|
||||
override fun toString() = "${position.toClickableStr()} Syntax error: $message"
|
||||
}
|
||||
|
||||
class ExpressionError(message: String, val position: Position) : AstException(message) {
|
||||
override fun toString() = "$position Error: $message"
|
||||
override fun toString() = "${position.toClickableStr()} Error: $message"
|
||||
}
|
||||
|
||||
class UndefinedSymbolError(symbol: IdentifierReference)
|
||||
|
@ -17,13 +17,13 @@ import kotlin.math.abs
|
||||
|
||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
|
||||
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>")
|
||||
|
||||
sealed class Expression: Node {
|
||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
abstract fun referencesIdentifiers(vararg name: String): Boolean
|
||||
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
|
||||
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||
|
||||
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
|
||||
@ -85,7 +85,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
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 referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val inferred = expression.inferType(program)
|
||||
return when(operator) {
|
||||
@ -142,7 +142,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
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 referencesIdentifier(vararg scopedName: String) = left.referencesIdentifier(*scopedName) || right.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val leftDt = left.inferType(program)
|
||||
val rightDt = right.inferType(program)
|
||||
@ -255,7 +255,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
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 referencesIdentifier(vararg scopedName: String) = identifier.referencesIdentifier(*scopedName)
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val target = identifier.targetStatement(program.namespace)
|
||||
@ -291,7 +291,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
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 referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
val cv = expression.constValue(program) ?: return null
|
||||
@ -322,7 +322,7 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
@ -345,7 +345,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
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 referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
|
||||
@ -398,7 +398,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun constValue(program: Program) = this
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
@ -498,7 +498,7 @@ class StringLiteralValue(val value: String,
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun referencesIdentifier(vararg scopedName: String) = false
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
@ -533,7 +533,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
||||
override fun referencesIdentifier(vararg scopedName: String) = value.any { it.referencesIdentifier(*scopedName) }
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
@ -569,10 +569,17 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
|
||||
return when {
|
||||
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
|
||||
DataType.STR in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||
DataType.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
|
||||
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
|
||||
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB)
|
||||
DataType.ARRAY_UW in dts ||
|
||||
DataType.ARRAY_W in dts ||
|
||||
DataType.ARRAY_UB in dts ||
|
||||
DataType.ARRAY_B in dts ||
|
||||
DataType.ARRAY_F in dts ||
|
||||
DataType.STRUCT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
|
||||
else -> InferredTypes.InferredType.unknown()
|
||||
}
|
||||
}
|
||||
@ -631,7 +638,7 @@ class RangeExpr(var from: Expression,
|
||||
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 referencesIdentifier(vararg scopedName: String): Boolean = from.referencesIdentifier(*scopedName) || to.referencesIdentifier(*scopedName)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val fromDt=from.inferType(program)
|
||||
val toDt=to.inferType(program)
|
||||
@ -744,7 +751,8 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
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 referencesIdentifier(vararg scopedName: String): Boolean =
|
||||
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
return when (val targetStmt = targetStatement(program.namespace)) {
|
||||
@ -766,6 +774,18 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
else -> throw FatalAstException("requires a reference value")
|
||||
}
|
||||
}
|
||||
|
||||
fun firstStructVarName(namespace: INameScope): String? {
|
||||
// take the name of the first struct member of the structvariable instead
|
||||
// if it's just a regular variable, return null.
|
||||
val struct = memberOfStruct(namespace) ?: return null
|
||||
val decl = targetVarDecl(namespace)!!
|
||||
val firstStructMember = struct.nameOfFirstMember()
|
||||
// find the flattened var that belongs to this first struct member
|
||||
val firstVarName = listOf(decl.name, firstStructMember)
|
||||
val firstVar = definingScope().lookup(firstVarName, this) as VarDecl
|
||||
return firstVar.name
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionCall(override var target: IdentifierReference,
|
||||
@ -832,7 +852,7 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
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 referencesIdentifier(vararg scopedName: String): Boolean = target.referencesIdentifier(*scopedName) || args.any{it.referencesIdentifier(*scopedName)}
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val constVal = constValue(program ,false)
|
||||
|
@ -334,8 +334,8 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
if(untilLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", untilLoop.untilCondition.position)
|
||||
if(untilLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", untilLoop.condition.position)
|
||||
super.visit(untilLoop)
|
||||
}
|
||||
|
||||
@ -376,6 +376,9 @@ internal class AstChecker(private val program: Program,
|
||||
if (sourceVar?.struct != null) {
|
||||
if (sourceVar.struct !== targetVar.struct)
|
||||
errors.err("assignment of different struct types", assignment.position)
|
||||
} else if(sourceVar?.isArray==true) {
|
||||
if((sourceVar.value as ArrayLiteralValue).value.size != targetVar.struct?.numberOfElements)
|
||||
errors.err("number of elements doesn't match struct definition", sourceVar.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -476,7 +479,7 @@ internal class AstChecker(private val program: Program,
|
||||
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true)
|
||||
err("recursive var declaration")
|
||||
|
||||
// CONST can only occur on simple types (byte, word, float)
|
||||
@ -584,13 +587,13 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(decl.value !is NumericLiteralValue) {
|
||||
err("value of memory var decl is not a numeric literal (it is a ${decl.value!!.javaClass.simpleName}).", decl.value?.position)
|
||||
} else {
|
||||
if(decl.value is NumericLiteralValue) {
|
||||
val value = decl.value as NumericLiteralValue
|
||||
if (value.type !in IntegerDatatypes || value.number.toInt() < 0 || value.number.toInt() > 65535) {
|
||||
err("memory address must be valid integer 0..\$ffff", decl.value?.position)
|
||||
}
|
||||
} else {
|
||||
err("value of memory mapped variable can only be a number, perhaps you meant to use an address pointer type instead?", decl.value?.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -709,7 +712,7 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
err("missing option directive argument(s)")
|
||||
else if(directive.args.map{it.name in setOf("enable_floats", "force_output")}.any { !it })
|
||||
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit")}.any { !it })
|
||||
err("invalid option directive argument(s)")
|
||||
}
|
||||
"%target" -> {
|
||||
@ -742,6 +745,17 @@ internal class AstChecker(private val program: Program,
|
||||
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array)
|
||||
}
|
||||
|
||||
fun isPassByReferenceElement(e: Expression): Boolean {
|
||||
if(e is IdentifierReference) {
|
||||
val decl = e.targetVarDecl(program.namespace)!!
|
||||
return decl.datatype in PassByReferenceDatatypes
|
||||
}
|
||||
return e is StringLiteralValue
|
||||
}
|
||||
|
||||
if(!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
|
||||
errors.err("array literal contains invalid types", array.position)
|
||||
|
||||
super.visit(array)
|
||||
}
|
||||
|
||||
@ -1098,7 +1112,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
if(value.type.isUnknown)
|
||||
return err("attempt to check values of array with as yet unknown datatype")
|
||||
return false
|
||||
|
||||
when (targetDt) {
|
||||
DataType.STR -> return err("string value expected")
|
||||
|
@ -34,6 +34,16 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
super.visit(block)
|
||||
}
|
||||
|
||||
override fun visit(directive: Directive) {
|
||||
if(directive.directive=="%target") {
|
||||
val compatibleTarget = directive.args.single().name
|
||||
if (compatibleTarget != CompilationTarget.instance.name)
|
||||
errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position)
|
||||
}
|
||||
|
||||
super.visit(directive)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||
|
||||
@ -88,14 +98,6 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
|
||||
if (existing != null && existing !== subroutine)
|
||||
nameError(subroutine.name, subroutine.position, existing)
|
||||
|
||||
// does the parameter redefine a variable declared elsewhere?
|
||||
for(param in subroutine.parameters) {
|
||||
val existingVar = subroutine.lookup(listOf(param.name), subroutine)
|
||||
if (existingVar != null && existingVar.parent !== subroutine) {
|
||||
nameError(param.name, param.position, existingVar)
|
||||
}
|
||||
}
|
||||
|
||||
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
|
@ -10,44 +10,6 @@ import prog8.ast.statements.*
|
||||
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource == listOf("swap")) {
|
||||
// TODO don't replace swap(), let the code generator figure this all out
|
||||
// if x and y are both just identifiers, do not rewrite (there should be asm generation for that)
|
||||
// otherwise:
|
||||
// rewrite swap(x,y) as follows:
|
||||
// - declare a temp variable of the same datatype
|
||||
// - temp = x, x = y, y= temp
|
||||
val first = functionCallStatement.args[0]
|
||||
val second = functionCallStatement.args[1]
|
||||
if(first !is IdentifierReference && second !is IdentifierReference) {
|
||||
val dt = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}"
|
||||
val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position)
|
||||
val tempvar = IdentifierReference(listOf(tempname), first.position)
|
||||
val assignTemp = Assignment(
|
||||
AssignTarget(tempvar, null, null, first.position),
|
||||
first,
|
||||
first.position
|
||||
)
|
||||
val assignFirst = Assignment(
|
||||
AssignTarget.fromExpr(first),
|
||||
second,
|
||||
first.position
|
||||
)
|
||||
val assignSecond = Assignment(
|
||||
AssignTarget.fromExpr(second),
|
||||
tempvar,
|
||||
first.position
|
||||
)
|
||||
val scope = AnonymousScope(mutableListOf(tempvardecl, assignTemp, assignFirst, assignSecond), first.position)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, scope, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// is it a struct variable? then define all its struct members as mangled names,
|
||||
// and include the original decl as well.
|
||||
|
@ -82,6 +82,7 @@ interface IAstModification {
|
||||
|
||||
class SwapOperands(val expr: BinaryExpression): IAstModification {
|
||||
override fun perform() {
|
||||
require(expr.operator in associativeOperators)
|
||||
val tmp = expr.left
|
||||
expr.left = expr.right
|
||||
expr.right = tmp
|
||||
@ -228,6 +229,7 @@ abstract class AstWalker {
|
||||
track(before(decl, parent), decl, parent)
|
||||
decl.value?.accept(this, decl)
|
||||
decl.arraysize?.accept(this, decl)
|
||||
decl.struct?.accept(this, decl)
|
||||
track(after(decl, parent), decl, parent)
|
||||
}
|
||||
|
||||
@ -348,7 +350,7 @@ abstract class AstWalker {
|
||||
|
||||
fun visit(untilLoop: UntilLoop, parent: Node) {
|
||||
track(before(untilLoop, parent), untilLoop, parent)
|
||||
untilLoop.untilCondition.accept(this, untilLoop)
|
||||
untilLoop.condition.accept(this, untilLoop)
|
||||
untilLoop.body.accept(this, untilLoop)
|
||||
track(after(untilLoop, parent), untilLoop, parent)
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ interface IAstVisitor {
|
||||
fun visit(decl: VarDecl) {
|
||||
decl.value?.accept(this)
|
||||
decl.arraysize?.accept(this)
|
||||
decl.struct?.accept(this)
|
||||
}
|
||||
|
||||
fun visit(subroutine: Subroutine) {
|
||||
@ -115,7 +116,7 @@ interface IAstVisitor {
|
||||
}
|
||||
|
||||
fun visit(untilLoop: UntilLoop) {
|
||||
untilLoop.untilCondition.accept(this)
|
||||
untilLoop.condition.accept(this)
|
||||
untilLoop.body.accept(this)
|
||||
}
|
||||
|
||||
|
@ -84,7 +84,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val valueType = assignment.value.inferType(program)
|
||||
val targetType = assignment.target.inferType(program, assignment)
|
||||
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
|
||||
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
|
||||
val assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
|
||||
} else {
|
||||
@ -179,26 +179,47 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
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(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
when {
|
||||
sourceVar.struct!=null -> {
|
||||
// struct memberwise copy
|
||||
val sourceStruct = sourceVar.struct!!
|
||||
if(sourceStruct!==targetVar.struct) {
|
||||
// structs are not the same in assignment
|
||||
return listOf() // error will be printed elsewhere
|
||||
}
|
||||
if(struct.statements.size!=sourceStruct.statements.size)
|
||||
return listOf() // error will be printed elsewhere
|
||||
return struct.statements.zip(sourceStruct.statements).map { member ->
|
||||
val targetDecl = member.first as VarDecl
|
||||
val sourceDecl = member.second as VarDecl
|
||||
if(targetDecl.name != sourceDecl.name)
|
||||
throw FatalAstException("struct member mismatch")
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
sourceVar.isArray -> {
|
||||
val array = (sourceVar.value as ArrayLiteralValue).value
|
||||
if(struct.statements.size!=array.size)
|
||||
return listOf() // error will be printed elsewhere
|
||||
return struct.statements.zip(array).map {
|
||||
val decl = it.first as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, decl.name)
|
||||
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val target = AssignTarget(targetName, null, null, structAssignment.position)
|
||||
val assign = Assignment(target, it.second, structAssignment.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
throw FatalAstException("can only assign arrays or structs to structs")
|
||||
}
|
||||
}
|
||||
}
|
||||
is ArrayLiteralValue -> {
|
||||
|
@ -26,6 +26,19 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private fun argTypeCompatible(argDt: DataType, paramDt: DataType): Boolean {
|
||||
if(argDt==paramDt)
|
||||
return true
|
||||
|
||||
// there are some exceptions that are considered compatible, such as STR <> UWORD
|
||||
if(argDt==DataType.STR && paramDt==DataType.UWORD ||
|
||||
argDt==DataType.UWORD && paramDt==DataType.STR)
|
||||
return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
|
||||
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
|
||||
val target = call.target.targetStatement(scope)
|
||||
@ -34,7 +47,7 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
if(call.args.size != target.parameters.size)
|
||||
return "invalid number of arguments"
|
||||
val paramtypes = target.parameters.map { it.type }
|
||||
val mismatch = argtypes.zip(paramtypes).indexOfFirst { it.first != it.second}
|
||||
val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
|
||||
if(mismatch>=0) {
|
||||
val actual = argtypes[mismatch].toString()
|
||||
val expected = paramtypes[mismatch].toString()
|
||||
@ -47,7 +60,8 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
return "invalid number of arguments"
|
||||
val paramtypes = func.parameters.map { it.possibleDatatypes }
|
||||
for (x in argtypes.zip(paramtypes).withIndex()) {
|
||||
if (x.value.first !in x.value.second) {
|
||||
val anyCompatible = x.value.second.any { argTypeCompatible(x.value.first, it) }
|
||||
if (!anyCompatible) {
|
||||
val actual = x.value.first.toString()
|
||||
val expected = x.value.second.toString()
|
||||
return "argument ${x.index + 1} type mismatch, was: $actual expected: $expected"
|
||||
|
@ -5,6 +5,7 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
|
||||
|
||||
sealed class Statement : Node {
|
||||
@ -214,8 +215,9 @@ open class VarDecl(val type: VarDeclType,
|
||||
DataType.UWORD -> DataType.ARRAY_UW
|
||||
DataType.WORD -> DataType.ARRAY_W
|
||||
DataType.FLOAT -> DataType.ARRAY_F
|
||||
DataType.STR -> DataType.ARRAY_UW // use memory address of the string instead
|
||||
else -> {
|
||||
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
|
||||
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats/strings(ptrs)", position))
|
||||
DataType.ARRAY_UB
|
||||
}
|
||||
}
|
||||
@ -301,6 +303,8 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
||||
}
|
||||
|
||||
fun constIndex() = (index as? NumericLiteralValue)?.number?.toInt()
|
||||
|
||||
infix fun isSameAs(other: ArrayIndex) = index.isSameAs(other.index)
|
||||
}
|
||||
|
||||
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
|
||||
@ -391,8 +395,8 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===identifier -> identifier = replacement as IdentifierReference
|
||||
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
|
||||
node === identifier -> identifier = replacement as IdentifierReference
|
||||
node === arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
@ -413,16 +417,16 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
}
|
||||
|
||||
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
|
||||
if(identifier!=null) {
|
||||
if (identifier != null) {
|
||||
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
|
||||
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
|
||||
}
|
||||
|
||||
if(arrayindexed!=null) {
|
||||
if (arrayindexed != null) {
|
||||
return arrayindexed!!.inferType(program)
|
||||
}
|
||||
|
||||
if(memoryAddress!=null)
|
||||
if (memoryAddress != null)
|
||||
return InferredTypes.knownFor(DataType.UBYTE)
|
||||
|
||||
return InferredTypes.unknown()
|
||||
@ -430,69 +434,93 @@ data class AssignTarget(var identifier: IdentifierReference?,
|
||||
|
||||
fun toExpression(): Expression {
|
||||
return when {
|
||||
identifier!=null -> identifier!!
|
||||
arrayindexed!=null -> arrayindexed!!
|
||||
memoryAddress!=null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
|
||||
identifier != null -> identifier!!
|
||||
arrayindexed != null -> arrayindexed!!
|
||||
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
|
||||
else -> throw FatalAstException("invalid assignmenttarget $this")
|
||||
}
|
||||
}
|
||||
|
||||
infix fun isSameAs(value: Expression): Boolean {
|
||||
return when {
|
||||
this.memoryAddress!=null -> {
|
||||
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)
|
||||
if (value is DirectMemoryRead)
|
||||
this.memoryAddress.addressExpression isSameAs value.addressExpression
|
||||
else
|
||||
false
|
||||
}
|
||||
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
|
||||
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
||||
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
|
||||
value.arrayspec.constIndex()!=null &&
|
||||
arrayindexed!!.arrayspec.constIndex()!=null &&
|
||||
value.arrayspec.constIndex()==arrayindexed!!.arrayspec.constIndex()
|
||||
identifier != null -> value is IdentifierReference && value.nameInSource == identifier!!.nameInSource
|
||||
arrayindexed != null -> {
|
||||
if(value is ArrayIndexedExpression && value.identifier.nameInSource == arrayindexed!!.identifier.nameInSource)
|
||||
arrayindexed!!.arrayspec isSameAs value.arrayspec
|
||||
else
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun isSameAs(other: AssignTarget, program: Program): Boolean {
|
||||
if(this===other)
|
||||
if (this === other)
|
||||
return true
|
||||
if(this.identifier!=null && other.identifier!=null)
|
||||
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
|
||||
if(this.memoryAddress!=null && other.memoryAddress!=null) {
|
||||
if (this.identifier != null && other.identifier != null)
|
||||
return this.identifier!!.nameInSource == other.identifier!!.nameInSource
|
||||
if (this.memoryAddress != null && other.memoryAddress != null) {
|
||||
val addr1 = this.memoryAddress.addressExpression.constValue(program)
|
||||
val addr2 = other.memoryAddress.addressExpression.constValue(program)
|
||||
return addr1!=null && addr2!=null && addr1==addr2
|
||||
return addr1 != null && addr2 != null && addr1 == addr2
|
||||
}
|
||||
if(this.arrayindexed!=null && other.arrayindexed!=null) {
|
||||
if(this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
|
||||
if (this.arrayindexed != null && other.arrayindexed != null) {
|
||||
if (this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
|
||||
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
|
||||
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
|
||||
return x1!=null && x2!=null && x1==x2
|
||||
return x1 != null && x2 != null && x1 == x2
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun isNotMemory(namespace: INameScope): Boolean {
|
||||
if(this.memoryAddress!=null)
|
||||
return false
|
||||
if(this.arrayindexed!=null) {
|
||||
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
|
||||
if(targetStmt!=null)
|
||||
return targetStmt.type!= VarDeclType.MEMORY
|
||||
fun isInRegularRAM(namespace: INameScope): Boolean {
|
||||
when {
|
||||
this.memoryAddress != null -> {
|
||||
return when (this.memoryAddress.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
CompilationTarget.instance.machine.isRegularRAMaddress((this.memoryAddress.addressExpression as NumericLiteralValue).number.toInt())
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val decl = (this.memoryAddress.addressExpression as IdentifierReference).targetVarDecl(namespace)
|
||||
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
|
||||
CompilationTarget.instance.machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
||||
else
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
this.arrayindexed != null -> {
|
||||
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
|
||||
return if (targetStmt?.type == VarDeclType.MEMORY) {
|
||||
val addr = targetStmt.value as? NumericLiteralValue
|
||||
if (addr != null)
|
||||
CompilationTarget.instance.machine.isRegularRAMaddress(addr.number.toInt())
|
||||
else
|
||||
false
|
||||
} else true
|
||||
}
|
||||
this.identifier != null -> {
|
||||
val decl = this.identifier!!.targetVarDecl(namespace)!!
|
||||
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
|
||||
CompilationTarget.instance.machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
|
||||
else
|
||||
true
|
||||
}
|
||||
else -> return true
|
||||
}
|
||||
if(this.identifier!=null) {
|
||||
val targetStmt = this.identifier!!.targetVarDecl(namespace)
|
||||
if(targetStmt!=null)
|
||||
return targetStmt.type!= VarDeclType.MEMORY
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -815,19 +843,19 @@ class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override
|
||||
}
|
||||
|
||||
class UntilLoop(var body: AnonymousScope,
|
||||
var untilCondition: Expression,
|
||||
var condition: Expression,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
untilCondition.linkParents(this)
|
||||
condition.linkParents(this)
|
||||
body.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
when {
|
||||
node===untilCondition -> untilCondition = replacement as Expression
|
||||
node===condition -> condition = replacement as Expression
|
||||
node===body -> body = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
|
@ -15,9 +15,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.add(Pair(decl.name, decl))
|
||||
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// a numeric vardecl without an initial value is initialized with zero.
|
||||
decl.value = decl.zeroElementValue()
|
||||
// a numeric vardecl without an initial value is initialized with zero,
|
||||
// unless there's already an assignment below, that initializes the value
|
||||
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
|
||||
if(nextAssign!=null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
|
||||
decl.value = null
|
||||
else
|
||||
decl.value = decl.zeroElementValue()
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
@ -25,53 +31,78 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// Try to replace A = B <operator> Something by A= B, A = A <operator> Something
|
||||
// this triggers the more efficent augmented assignment code generation more often.
|
||||
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& assignment.target.isNotMemory(program.namespace)) {
|
||||
&& assignment.target.isInRegularRAM(program.namespace)) {
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null && binExpr.operator !in comparisonOperators) {
|
||||
if(binExpr.left !is BinaryExpression) {
|
||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
if (binExpr != null && binExpr.operator !in comparisonOperators) {
|
||||
if (binExpr.left !is BinaryExpression) {
|
||||
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
|
||||
// the right part of the expression contains the target variable itself.
|
||||
// we can't 'split' it trivially because the variable will be changed halfway through.
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
|
||||
// use the other part of the expression to split.
|
||||
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
} else {
|
||||
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
subroutineVariables.clear()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||
subroutineVariables.addAll(decls.map { Pair(it.name, it) })
|
||||
|
||||
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) {
|
||||
// move vardecls of the scope into the upper scope. Make sure the order remains the same!
|
||||
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }.reversed()
|
||||
return numericVarsWithValue.map {
|
||||
val initValue = it.value!! // assume here that value has always been set by now
|
||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||
val assign = Assignment(target, initValue, it.position)
|
||||
initValue.parent = assign
|
||||
IAstModification.InsertFirst(assign, scope)
|
||||
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
|
||||
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
|
||||
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
|
||||
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
|
||||
val replaceVardecls =numericVarsWithValue.map {
|
||||
val initValue = it.value!! // assume here that value has always been set by now
|
||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||
val assign = Assignment(target, initValue, it.position)
|
||||
initValue.parent = assign
|
||||
IAstModification.ReplaceNode(it, assign, scope)
|
||||
}
|
||||
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
|
||||
return replaceVardecls + moveVardeclsUp
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val firstDeclarations = mutableMapOf<String, VarDecl>()
|
||||
for(decl in subroutineVariables) {
|
||||
val existing = firstDeclarations[decl.first]
|
||||
if(existing!=null && existing !== decl.second) {
|
||||
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
|
||||
} else {
|
||||
firstDeclarations[decl.first] = decl.second
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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>()
|
||||
@ -95,7 +126,6 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
&& outerScope !is Block) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
@ -143,4 +173,34 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
val binExpr = ifStatement.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// if x -> if x!=0, if x+5 -> if x+5 != 0
|
||||
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val binExpr = untilLoop.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// until x -> until x!=0, until x+5 -> until x+5 != 0
|
||||
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val binExpr = whileLoop.condition as? BinaryExpression
|
||||
if(binExpr==null || binExpr.operator !in comparisonOperators) {
|
||||
// while x -> while x!=0, while x+5 -> while x+5 != 0
|
||||
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ data class CompilationOptions(val output: OutputType,
|
||||
val zeropage: ZeropageType,
|
||||
val zpReserved: List<IntRange>,
|
||||
val floats: Boolean,
|
||||
val compilationTarget: String?)
|
||||
val noSysInit: Boolean)
|
||||
|
||||
|
||||
class CompilerException(message: String?) : Exception(message)
|
||||
|
@ -7,6 +7,7 @@ import prog8.ast.statements.Directive
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.Cx16Target
|
||||
import prog8.optimizer.*
|
||||
import prog8.optimizer.UnusedCodeRemover
|
||||
import prog8.optimizer.constantFold
|
||||
import prog8.optimizer.optimizeStatements
|
||||
@ -91,7 +92,7 @@ fun compileProgram(filepath: Path,
|
||||
}
|
||||
|
||||
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
|
||||
println("Parsing...")
|
||||
println("Compiler target: ${CompilationTarget.instance.name}. Parsing...")
|
||||
val importer = ModuleImporter()
|
||||
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||
importer.importModule(programAst, filepath)
|
||||
@ -125,7 +126,8 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
|
||||
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
|
||||
val zpType: ZeropageType =
|
||||
val noSysInit = allOptions.any { it.name == "no_sysinit" }
|
||||
var zpType: ZeropageType =
|
||||
if (zpoption == null)
|
||||
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
|
||||
else
|
||||
@ -135,6 +137,12 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
ZeropageType.KERNALSAFE
|
||||
// error will be printed by the astchecker
|
||||
}
|
||||
|
||||
if (zpType==ZeropageType.FLOATSAFE && CompilationTarget.instance.name == Cx16Target.name) {
|
||||
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
|
||||
zpType = ZeropageType.BASICSAFE
|
||||
}
|
||||
|
||||
val zpReserved = mainModule.statements
|
||||
.asSequence()
|
||||
.filter { it is Directive && it.directive == "%zpreserved" }
|
||||
@ -142,20 +150,19 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
.map { it[0].int!!..it[1].int!! }
|
||||
.toList()
|
||||
|
||||
var target = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%target" }
|
||||
as? Directive)?.args?.single()?.name
|
||||
|
||||
when(target) {
|
||||
C64Target.name -> CompilationTarget.instance = C64Target
|
||||
Cx16Target.name -> CompilationTarget.instance = Cx16Target
|
||||
null -> target = CompilationTarget.instance.name
|
||||
else -> throw FatalAstException("invalid target")
|
||||
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
|
||||
System.err.println("invalid output type $outputType")
|
||||
exitProcess(1)
|
||||
}
|
||||
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
|
||||
System.err.println("invalid launcher type $launcherType")
|
||||
exitProcess(1)
|
||||
}
|
||||
|
||||
return CompilationOptions(
|
||||
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
|
||||
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
|
||||
zpType, zpReserved, floatsEnabled, target
|
||||
zpType, zpReserved, floatsEnabled, noSysInit
|
||||
)
|
||||
}
|
||||
|
||||
@ -183,13 +190,13 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = programAst.simplifyExpressions()
|
||||
val optsDone2 = programAst.optimizeStatements(errors)
|
||||
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away:
|
||||
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
|
||||
errors.handle()
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
break
|
||||
}
|
||||
|
||||
val remover = UnusedCodeRemover(errors)
|
||||
val remover = UnusedCodeRemover(programAst, errors)
|
||||
remover.visit(programAst)
|
||||
remover.applyModifications()
|
||||
errors.handle()
|
||||
@ -214,9 +221,6 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
if(compilerOptions.compilationTarget!=null && compilerOptions.compilationTarget != CompilationTarget.instance.name)
|
||||
throw AssemblyError("program's compilation target differs from configured target")
|
||||
|
||||
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
|
||||
val assembly = CompilationTarget.instance.asmGenerator(
|
||||
programAst,
|
||||
|
@ -70,12 +70,4 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
|
||||
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
|
||||
|
||||
enum class ExitProgramStrategy {
|
||||
CLEAN_EXIT,
|
||||
SYSTEM_RESET
|
||||
}
|
||||
|
||||
abstract val exitProgramStrategy: ExitProgramStrategy
|
||||
|
||||
}
|
||||
|
@ -17,8 +17,6 @@ internal interface CompilationTarget {
|
||||
fun encodeString(str: String, altEncoding: Boolean): List<Short>
|
||||
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
|
||||
fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator
|
||||
val asmForSystemReset: String
|
||||
val initProcName: String?
|
||||
|
||||
companion object {
|
||||
lateinit var instance: CompilationTarget
|
||||
@ -35,8 +33,6 @@ internal object C64Target: CompilationTarget {
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
|
||||
AsmGen(program, errors, zp, options, path)
|
||||
override val asmForSystemReset = " sei | lda #14 | sta $1 | jmp (c64.RESET_VEC)"
|
||||
override val initProcName = "c64.init_system"
|
||||
}
|
||||
|
||||
internal object Cx16Target: CompilationTarget {
|
||||
@ -48,6 +44,4 @@ internal object Cx16Target: CompilationTarget {
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
|
||||
AsmGen(program, errors, zp, options, path)
|
||||
override val asmForSystemReset = " sei | stz cx16.d1prb | jmp (cx16.RESET_VEC)"
|
||||
override val initProcName = "cx16.init_system"
|
||||
}
|
||||
|
@ -35,4 +35,5 @@ internal interface IMachineDefinition {
|
||||
fun getFloatRomConst(number: Double): String?
|
||||
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
|
||||
fun launchEmulator(programName: String)
|
||||
fun isRegularRAMaddress(address: Int): Boolean
|
||||
}
|
||||
|
@ -37,28 +37,29 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
val mflpt5 = Mflpt5.fromNumber(number)
|
||||
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
|
||||
when {
|
||||
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.ZERO" // not a ROM const
|
||||
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "c64flt.FL_PIVAL"
|
||||
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_N32768"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FONE"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRHLF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRTWO"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_NEGHLF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "c64flt.FL_LOG2"
|
||||
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "c64flt.FL_TENC"
|
||||
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "c64flt.FL_NZMIL"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FHALF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "c64flt.FL_LOGEB2"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_PIHALF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_TWOPI"
|
||||
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FR4"
|
||||
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ZERO_const" // not a ROM const
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ONE_const" // not a ROM const
|
||||
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "floats.FL_PIVAL"
|
||||
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_N32768"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FONE"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRHLF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRTWO"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_NEGHLF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "floats.FL_LOG2"
|
||||
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "floats.FL_TENC"
|
||||
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "floats.FL_NZMIL"
|
||||
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FHALF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "floats.FL_LOGEB2"
|
||||
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_PIHALF"
|
||||
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_TWOPI"
|
||||
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FR4"
|
||||
else -> {
|
||||
// attempt to correct for a few rounding issues
|
||||
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
|
||||
3.1415926536 -> return "c64flt.FL_PIVAL"
|
||||
1.4142135624 -> return "c64flt.FL_SQRTWO"
|
||||
0.7071067812 -> return "c64flt.FL_SQRHLF"
|
||||
0.6931471806 -> return "c64flt.FL_LOG2"
|
||||
3.1415926536 -> return "floats.FL_PIVAL"
|
||||
1.4142135624 -> return "floats.FL_SQRTWO"
|
||||
0.7071067812 -> return "floats.FL_SQRHLF"
|
||||
0.6931471806 -> return "floats.FL_LOG2"
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@ -68,7 +69,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
importer.importLibraryModule(program, "c64lib")
|
||||
importer.importLibraryModule(program, "syslib")
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
@ -88,6 +89,8 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
}
|
||||
@ -112,12 +115,6 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
|
||||
|
||||
|
||||
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
|
||||
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
|
||||
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
|
||||
@ -147,7 +144,7 @@ internal object C64MachineDefinition: IMachineDefinition {
|
||||
if (options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zero page locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
|
@ -47,10 +47,10 @@ internal class AsmGen(private val program: Program,
|
||||
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
|
||||
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
|
||||
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this)
|
||||
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
|
||||
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
|
||||
assemblyLines.clear()
|
||||
@ -81,6 +81,7 @@ internal class AsmGen(private val program: Program,
|
||||
return AssemblyProgram(program.name, outputDir)
|
||||
}
|
||||
|
||||
|
||||
private fun header() {
|
||||
val ourName = this.javaClass.name
|
||||
val cpu = when(CompilationTarget.instance.machine.cpu) {
|
||||
@ -120,18 +121,14 @@ 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")
|
||||
if(!CompilationTarget.instance.initProcName.isNullOrEmpty())
|
||||
out(" jsr ${CompilationTarget.instance.initProcName}")
|
||||
if(!options.noSysInit)
|
||||
out(" jsr ${CompilationTarget.instance.name}.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")
|
||||
if(!CompilationTarget.instance.initProcName.isNullOrEmpty())
|
||||
out(" jsr ${CompilationTarget.instance.initProcName}")
|
||||
if(!options.noSysInit)
|
||||
out(" jsr ${CompilationTarget.instance.name}.init_system")
|
||||
}
|
||||
options.output == OutputType.RAW -> {
|
||||
out("; ---- raw assembler program ----")
|
||||
@ -139,33 +136,16 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if (zeropage.exitProgramStrategy != Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
|
||||
// disable shift-commodore charset switching and run/stop key
|
||||
out(" lda #$80")
|
||||
out(" lda #$80")
|
||||
out(" sta 657\t; disable charset switching")
|
||||
out(" lda #239")
|
||||
out(" sta 808\t; disable run/stop key")
|
||||
if(options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
|
||||
out("""
|
||||
; zeropage is clobbered so we need to reset the machine at exit
|
||||
lda #>${CompilationTarget.instance.name}.reset_system
|
||||
pha
|
||||
lda #<${CompilationTarget.instance.name}.reset_system
|
||||
pha""")
|
||||
}
|
||||
|
||||
out(" ldx #\$ff\t; init estack pointer")
|
||||
|
||||
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")
|
||||
when (zeropage.exitProgramStrategy) {
|
||||
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
|
||||
out(" jmp main.start\t; jump to program entrypoint")
|
||||
}
|
||||
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
|
||||
out(" jsr main.start\t; call program entrypoint")
|
||||
out(CompilationTarget.instance.asmForSystemReset)
|
||||
}
|
||||
}
|
||||
out(" jmp main.start ; start program / force start proc to be included")
|
||||
}
|
||||
|
||||
private fun footer() {
|
||||
@ -355,7 +335,10 @@ internal class AsmGen(private val program: Program,
|
||||
out("\n; memdefs and kernel subroutines")
|
||||
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
|
||||
for(m in memvars) {
|
||||
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
|
||||
if(m.value is NumericLiteralValue)
|
||||
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
|
||||
else
|
||||
out(" ${m.name} = ${asmVariableName((m.value as AddressOf).identifier)}")
|
||||
}
|
||||
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
|
||||
for(sub in asmSubs) {
|
||||
@ -423,10 +406,17 @@ internal class AsmGen(private val program: Program,
|
||||
"$"+number.toString(16).padStart(2, '0')
|
||||
}
|
||||
DataType.ARRAY_UW -> array.map {
|
||||
if(it is NumericLiteralValue) {
|
||||
"$" + it.number.toInt().toString(16).padStart(4, '0')
|
||||
} else {
|
||||
(it as AddressOf).identifier.nameInSource.joinToString(".")
|
||||
when (it) {
|
||||
is NumericLiteralValue -> {
|
||||
"$" + it.number.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
is AddressOf -> {
|
||||
it.identifier.firstStructVarName(program.namespace) ?: asmSymbolName(it.identifier)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
it.firstStructVarName(program.namespace) ?: asmSymbolName(it)
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid arraysize type")
|
||||
@ -558,26 +548,11 @@ internal class AsmGen(private val program: Program,
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
|
||||
else {
|
||||
val save = makeLabel("saveX")
|
||||
saveRegisterLabels.push(save)
|
||||
out("""
|
||||
stx $save
|
||||
jmp +
|
||||
$save .byte 0
|
||||
+""")
|
||||
}
|
||||
else out(" stx _prog8_regsave${register.name}")
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
|
||||
else {
|
||||
val save = makeLabel("saveY")
|
||||
out("""
|
||||
sty $save
|
||||
jmp +
|
||||
$save .byte 0
|
||||
+""")
|
||||
}
|
||||
else out(" sty _prog8_regsave${register.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -587,17 +562,11 @@ $save .byte 0
|
||||
CpuRegister.A -> out(" pla")
|
||||
CpuRegister.X -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
|
||||
else {
|
||||
val save = saveRegisterLabels.pop()
|
||||
out(" ldx $save")
|
||||
}
|
||||
else out(" ldx _prog8_regsave${register.name}")
|
||||
}
|
||||
CpuRegister.Y -> {
|
||||
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
|
||||
else {
|
||||
val save = saveRegisterLabels.pop()
|
||||
out(" ldy $save")
|
||||
}
|
||||
else out(" ldy _prog8_regsave${register.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -616,15 +585,15 @@ $save .byte 0
|
||||
is FunctionCallStatement -> {
|
||||
val functionName = stmt.target.nameInSource.last()
|
||||
val builtinFunc = BuiltinFunctions[functionName]
|
||||
if(builtinFunc!=null) {
|
||||
if (builtinFunc != null) {
|
||||
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
|
||||
} else {
|
||||
functioncallAsmGen.translateFunctionCall(stmt)
|
||||
// discard any results from the stack:
|
||||
val sub = stmt.target.targetSubroutine(program.namespace)!!
|
||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||
for((t, reg) in returns) {
|
||||
if(reg.stack) {
|
||||
for ((t, reg) in returns) {
|
||||
if (reg.stack) {
|
||||
if (t in IntegerDatatypes || t in PassByReferenceDatatypes) out(" inx")
|
||||
else if (t == DataType.FLOAT) out(" inx | inx | inx")
|
||||
}
|
||||
@ -749,10 +718,40 @@ $save .byte 0
|
||||
}
|
||||
else {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
|
||||
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
|
||||
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
when (register) {
|
||||
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
|
||||
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
|
||||
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
asl a""")
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc P8ESTACK_LO,x""")
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -787,9 +786,30 @@ $save .byte 0
|
||||
out("${sub.name}\t.proc")
|
||||
zeropagevars2asm(sub.statements)
|
||||
memdefs2asm(sub.statements)
|
||||
|
||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
||||
if(sub.name=="start" && sub.definingBlock().name=="main") {
|
||||
out("; program startup initialization")
|
||||
out(" cld")
|
||||
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("""
|
||||
tsx
|
||||
stx prog8_lib.orig_stackpointer ; required for func_exit
|
||||
ldx #255 ; init estack ptr
|
||||
clv
|
||||
clc""")
|
||||
}
|
||||
|
||||
out("; statements")
|
||||
sub.statements.forEach{ translate(it) }
|
||||
out("; variables")
|
||||
out("""
|
||||
; register saves
|
||||
_prog8_regsaveX .byte 0
|
||||
_prog8_regsaveY .byte 0""") // TODO only generate these bytes if they're actually used by saveRegister()
|
||||
vardecls2asm(sub.statements)
|
||||
out(" .pend\n")
|
||||
}
|
||||
@ -821,47 +841,32 @@ $save .byte 0
|
||||
}
|
||||
|
||||
private fun translate(stmt: IfStatement) {
|
||||
when {
|
||||
stmt.elsepart.containsNoCodeNorVars() -> {
|
||||
// empty else
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
|
||||
val endLabel = makeLabel("if_end")
|
||||
out(" beq $endLabel")
|
||||
translate(stmt.truepart)
|
||||
out(endLabel)
|
||||
}
|
||||
stmt.truepart.containsNoCodeNorVars() -> {
|
||||
// empty true part
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
|
||||
val endLabel = makeLabel("if_end")
|
||||
out(" bne $endLabel")
|
||||
translate(stmt.elsepart)
|
||||
out(endLabel)
|
||||
}
|
||||
else -> {
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
|
||||
val elseLabel = makeLabel("if_else")
|
||||
val endLabel = makeLabel("if_end")
|
||||
out(" beq $elseLabel")
|
||||
translate(stmt.truepart)
|
||||
out(" jmp $endLabel")
|
||||
out(elseLabel)
|
||||
translate(stmt.elsepart)
|
||||
out(endLabel)
|
||||
}
|
||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||
val booleanCondition = stmt.condition as BinaryExpression
|
||||
|
||||
if (stmt.elsepart.containsNoCodeNorVars()) {
|
||||
// empty else
|
||||
val endLabel = makeLabel("if_end")
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
|
||||
translate(stmt.truepart)
|
||||
out(endLabel)
|
||||
}
|
||||
else {
|
||||
// both true and else parts
|
||||
val elseLabel = makeLabel("if_else")
|
||||
val endLabel = makeLabel("if_end")
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, elseLabel)
|
||||
translate(stmt.truepart)
|
||||
out(" jmp $endLabel")
|
||||
out(elseLabel)
|
||||
translate(stmt.elsepart)
|
||||
out(endLabel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateTestStack(dataType: DataType) {
|
||||
when(dataType) {
|
||||
in ByteDatatypes -> out(" inx | lda P8ESTACK_LO,x")
|
||||
in WordDatatypes -> out(" inx | lda P8ESTACK_LO,x | ora P8ESTACK_HI,x")
|
||||
DataType.FLOAT -> throw AssemblyError("conditional value should be an integer (boolean)")
|
||||
else -> throw AssemblyError("non-numerical dt")
|
||||
}
|
||||
private fun checkBooleanExpression(condition: Expression) {
|
||||
if(condition !is BinaryExpression || condition.operator !in comparisonOperators)
|
||||
throw AssemblyError("expected boolean expression $condition")
|
||||
}
|
||||
|
||||
private fun translate(stmt: RepeatLoop) {
|
||||
@ -980,25 +985,13 @@ $counterVar .byte 0""")
|
||||
}
|
||||
|
||||
private fun translate(stmt: WhileLoop) {
|
||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||
val booleanCondition = stmt.condition as BinaryExpression
|
||||
val whileLabel = makeLabel("while")
|
||||
val endLabel = makeLabel("whileend")
|
||||
loopEndLabels.push(endLabel)
|
||||
out(whileLabel)
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
val conditionDt = stmt.condition.inferType(program)
|
||||
if(!conditionDt.isKnown)
|
||||
throw AssemblyError("unknown condition dt")
|
||||
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
|
||||
out(" inx | lda P8ESTACK_LO,x | beq $endLabel")
|
||||
} else {
|
||||
out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
bne +
|
||||
lda P8ESTACK_HI,x
|
||||
beq $endLabel
|
||||
+ """)
|
||||
}
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
|
||||
translate(stmt.body)
|
||||
out(" jmp $whileLabel")
|
||||
out(endLabel)
|
||||
@ -1006,26 +999,14 @@ $counterVar .byte 0""")
|
||||
}
|
||||
|
||||
private fun translate(stmt: UntilLoop) {
|
||||
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
|
||||
val booleanCondition = stmt.condition as BinaryExpression
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val endLabel = makeLabel("repeatend")
|
||||
loopEndLabels.push(endLabel)
|
||||
out(repeatLabel)
|
||||
translate(stmt.body)
|
||||
expressionsAsmGen.translateExpression(stmt.untilCondition)
|
||||
val conditionDt = stmt.untilCondition.inferType(program)
|
||||
if(!conditionDt.isKnown)
|
||||
throw AssemblyError("unknown condition dt")
|
||||
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
|
||||
out(" inx | lda P8ESTACK_LO,x | beq $repeatLabel")
|
||||
} else {
|
||||
out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
bne +
|
||||
lda P8ESTACK_HI,x
|
||||
beq $repeatLabel
|
||||
+ """)
|
||||
}
|
||||
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, repeatLabel)
|
||||
out(endLabel)
|
||||
loopEndLabels.pop()
|
||||
}
|
||||
@ -1065,6 +1046,7 @@ $counterVar .byte 0""")
|
||||
}
|
||||
}
|
||||
}
|
||||
out(" jmp $endLabel")
|
||||
for(choiceBlock in choiceBlocks) {
|
||||
out(choiceBlock.first)
|
||||
translate(choiceBlock.second)
|
||||
|
@ -152,7 +152,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
|
||||
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
|
||||
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
|
||||
(seventh.startsWith("jsr c64flt.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
||||
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
val nineth = pair[8].value.trimStart()
|
||||
val tenth = pair[9].value.trimStart()
|
||||
@ -163,7 +163,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
|
||||
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
|
||||
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
|
||||
(fourteenth.startsWith("jsr c64flt.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
|
||||
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
||||
// identical float init
|
||||
@ -179,6 +179,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
}
|
||||
|
||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
// TODO not sure if this is correct in all situations....:
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (pair in linesByFour) {
|
||||
|
@ -4,8 +4,14 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
|
||||
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
|
||||
import prog8.compiler.target.c64.codegen.assignment.SourceStorageKind
|
||||
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
|
||||
import prog8.compiler.toHex
|
||||
import prog8.functions.FSignature
|
||||
|
||||
@ -342,7 +348,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) {
|
||||
translateFunctionArguments(fcall.args, func)
|
||||
asmgen.out(" jsr c64flt.func_$functionName")
|
||||
asmgen.out(" jsr floats.func_$functionName")
|
||||
}
|
||||
|
||||
private fun funcSgn(fcall: IFunctionCall, func: FSignature) {
|
||||
@ -353,7 +359,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
|
||||
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
|
||||
DataType.WORD -> asmgen.out(" jsr math.sign_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr c64flt.sign_f")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.sign_f")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
}
|
||||
@ -364,7 +370,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${functionName}_f")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
}
|
||||
@ -377,7 +383,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
|
||||
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
|
||||
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${functionName}_f")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
}
|
||||
@ -395,12 +401,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
private fun funcSwap(fcall: IFunctionCall) {
|
||||
val first = fcall.args[0]
|
||||
val second = fcall.args[1]
|
||||
|
||||
// optimized simple case: swap two variables
|
||||
if(first is IdentifierReference && second is IdentifierReference) {
|
||||
val firstName = asmgen.asmVariableName(first)
|
||||
val secondName = asmgen.asmVariableName(second)
|
||||
val dt = first.inferType(program)
|
||||
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
|
||||
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | tya | sta $secondName")
|
||||
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName")
|
||||
return
|
||||
}
|
||||
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
|
||||
@ -426,14 +434,196 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$secondName
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr c64flt.swap_floats
|
||||
jsr floats.swap_floats
|
||||
""")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// other types of swap() calls should have been replaced by a different statement sequence involving a temp variable
|
||||
throw AssemblyError("no asm generation for swap funccall $fcall")
|
||||
// optimized simple case: swap two memory locations
|
||||
if(first is DirectMemoryRead && second is DirectMemoryRead) {
|
||||
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
|
||||
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
|
||||
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
|
||||
val name2 = if(second.addressExpression is IdentifierReference) asmgen.asmVariableName(second.addressExpression as IdentifierReference) else null
|
||||
|
||||
when {
|
||||
addr1!=null && addr2!=null -> {
|
||||
asmgen.out(" ldy $addr1 | lda $addr2 | sta $addr1 | sty $addr2")
|
||||
return
|
||||
}
|
||||
addr1!=null && name2!=null -> {
|
||||
asmgen.out(" ldy $addr1 | lda $name2 | sta $addr1 | sty $name2")
|
||||
return
|
||||
}
|
||||
name1!=null && addr2 != null -> {
|
||||
asmgen.out(" ldy $name1 | lda $addr2 | sta $name1 | sty $addr2")
|
||||
return
|
||||
}
|
||||
name1!=null && name2!=null -> {
|
||||
asmgen.out(" ldy $name1 | lda $name2 | sta $name1 | sty $name2")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(first is ArrayIndexedExpression && second is ArrayIndexedExpression) {
|
||||
val indexValue1 = first.arrayspec.index as? NumericLiteralValue
|
||||
val indexName1 = first.arrayspec.index as? IdentifierReference
|
||||
val indexValue2 = second.arrayspec.index as? NumericLiteralValue
|
||||
val indexName2 = second.arrayspec.index as? IdentifierReference
|
||||
val arrayVarName1 = asmgen.asmVariableName(first.identifier)
|
||||
val arrayVarName2 = asmgen.asmVariableName(second.identifier)
|
||||
val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
|
||||
if(indexValue1!=null && indexValue2!=null) {
|
||||
swapArrayValues(elementDt, arrayVarName1, indexValue1, arrayVarName2, indexValue2)
|
||||
return
|
||||
} else if(indexName1!=null && indexName2!=null) {
|
||||
swapArrayValues(elementDt, arrayVarName1, indexName1, arrayVarName2, indexName2)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// all other types of swap() calls are done via the evaluation stack
|
||||
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
|
||||
return when (expr) {
|
||||
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, variable=expr)
|
||||
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, array = expr)
|
||||
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, memory = DirectMemoryWrite(expr.addressExpression, expr.position))
|
||||
else -> throw AssemblyError("invalid expression object $expr")
|
||||
}
|
||||
}
|
||||
|
||||
asmgen.translateExpression(first)
|
||||
asmgen.translateExpression(second)
|
||||
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val assignFirst = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, datatype),
|
||||
targetFromExpr(first, datatype),
|
||||
false, first.position
|
||||
)
|
||||
val assignSecond = AsmAssignment(
|
||||
AsmAssignSource(SourceStorageKind.STACK, program, datatype),
|
||||
targetFromExpr(second, datatype),
|
||||
false, second.position
|
||||
)
|
||||
asmgen.translateNormalAssignment(assignFirst)
|
||||
asmgen.translateNormalAssignment(assignSecond)
|
||||
}
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
|
||||
val index1 = indexValue1.number.toInt() * elementDt.memorySize()
|
||||
val index2 = indexValue2.number.toInt() * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out("""
|
||||
lda $arrayVarName1+$index1
|
||||
ldy $arrayVarName2+$index2
|
||||
sta $arrayVarName2+$index2
|
||||
sty $arrayVarName1+$index1
|
||||
""")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out("""
|
||||
lda $arrayVarName1+$index1
|
||||
ldy $arrayVarName2+$index2
|
||||
sta $arrayVarName2+$index2
|
||||
sty $arrayVarName1+$index1
|
||||
lda $arrayVarName1+$index1+1
|
||||
ldy $arrayVarName2+$index2+1
|
||||
sta $arrayVarName2+$index2+1
|
||||
sty $arrayVarName1+$index1+1
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<(${arrayVarName1}+$index1)
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>(${arrayVarName1}+$index1)
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<(${arrayVarName2}+$index2)
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>(${arrayVarName2}+$index2)
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr floats.swap_floats
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid aray elt type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexName2: IdentifierReference) {
|
||||
val idxAsmName1 = asmgen.asmVariableName(indexName1)
|
||||
val idxAsmName2 = asmgen.asmVariableName(indexName2)
|
||||
when(elementDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out("""
|
||||
stx P8ZP_SCRATCH_REG
|
||||
ldx $idxAsmName1
|
||||
ldy $idxAsmName2
|
||||
lda $arrayVarName1,x
|
||||
pha
|
||||
lda $arrayVarName2,y
|
||||
sta $arrayVarName1,x
|
||||
pla
|
||||
sta $arrayVarName2,y
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
""")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out("""
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda $idxAsmName1
|
||||
asl a
|
||||
tax
|
||||
lda $idxAsmName2
|
||||
asl a
|
||||
tay
|
||||
lda $arrayVarName1,x
|
||||
pha
|
||||
lda $arrayVarName2,y
|
||||
sta $arrayVarName1,x
|
||||
pla
|
||||
sta $arrayVarName2,y
|
||||
lda $arrayVarName1+1,x
|
||||
pha
|
||||
lda $arrayVarName2+1,y
|
||||
sta $arrayVarName1+1,x
|
||||
pla
|
||||
sta $arrayVarName2+1,y
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #>$arrayVarName1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda $idxAsmName1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $idxAsmName1
|
||||
adc #<$arrayVarName1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ lda #>$arrayVarName2
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda $idxAsmName2
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $idxAsmName2
|
||||
adc #<$arrayVarName2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
bcc +
|
||||
inc P8ZP_SCRATCH_W2+1
|
||||
+ jsr floats.swap_floats
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid aray elt type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
|
||||
@ -442,7 +632,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.abs_f")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -33,7 +33,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$what | ldy #>$what")
|
||||
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
||||
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
@ -90,7 +90,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
||||
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
@ -121,7 +121,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
adc #<$asmArrayvarname
|
||||
bcc +
|
||||
iny
|
||||
+ jsr c64flt.inc_var_f""")
|
||||
+ jsr floats.inc_var_f""")
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
{
|
||||
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
|
||||
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
|
||||
val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! }
|
||||
val vardecl by lazy { variable!!.targetVarDecl(program.namespace)!! }
|
||||
val asmVarname by lazy {
|
||||
if(variable!=null)
|
||||
asmgen.asmVariableName(variable)
|
||||
|
@ -8,12 +8,13 @@ import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
|
||||
import prog8.compiler.toHex
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen, private val exprAsmgen: ExpressionsAsmGen) {
|
||||
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen)
|
||||
|
||||
fun translate(assignment: Assignment) {
|
||||
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
|
||||
@ -65,7 +66,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
in WordDatatypes ->
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
|
||||
DataType.FLOAT ->
|
||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
|
||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float")
|
||||
else ->
|
||||
throw AssemblyError("weird array type")
|
||||
}
|
||||
@ -87,7 +88,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
adc #<$arrayVarName
|
||||
bcc +
|
||||
iny
|
||||
+ jsr c64flt.push_float""")
|
||||
+ jsr floats.push_float""")
|
||||
}
|
||||
else ->
|
||||
throw AssemblyError("weird array elt type")
|
||||
@ -217,7 +218,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}
|
||||
jsr c64flt.pop_float
|
||||
jsr floats.pop_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
@ -249,7 +250,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
jsr c64flt.pop_float
|
||||
jsr floats.pop_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
@ -279,7 +280,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ jsr c64flt.pop_float""")
|
||||
+ jsr floats.pop_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
@ -319,18 +320,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
}
|
||||
|
||||
private fun assignAddressOf(target: AsmAssignTarget, name: IdentifierReference) {
|
||||
val struct = name.memberOfStruct(program.namespace)
|
||||
val sourceName = if (struct != null) {
|
||||
// take the address of the first struct member instead
|
||||
val decl = name.targetVarDecl(program.namespace)!!
|
||||
val firstStructMember = struct.nameOfFirstMember()
|
||||
// find the flattened var that belongs to this first struct member
|
||||
val firstVarName = listOf(decl.name, firstStructMember)
|
||||
val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl
|
||||
firstVar.name
|
||||
} else {
|
||||
asmgen.fixNameSymbols(name.nameInSource.joinToString("."))
|
||||
}
|
||||
val sourceName = name.firstStructVarName(program.namespace) ?: asmgen.fixNameSymbols(name.nameInSource.joinToString("."))
|
||||
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -406,7 +396,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
jsr c64flt.copy_float
|
||||
jsr floats.copy_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
@ -439,7 +429,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ jsr c64flt.copy_float""")
|
||||
+ jsr floats.copy_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
@ -496,13 +486,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||
// }
|
||||
val index = target.array!!.arrayspec.index
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr floats.push_float")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname} | jsr c64flt.pop_float_to_indexed_var")
|
||||
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname} | jsr floats.pop_float_to_indexed_var")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
|
||||
TargetStorageKind.STACK -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
|
||||
TargetStorageKind.STACK -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr floats.push_float")
|
||||
}
|
||||
}
|
||||
|
||||
@ -850,7 +840,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
jsr c64flt.set_0_array_float
|
||||
jsr floats.set_0_array_float
|
||||
""")
|
||||
}
|
||||
}
|
||||
@ -858,7 +848,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
|
||||
TargetStorageKind.STACK -> {
|
||||
val floatConst = asmgen.getFloatAsmConst(float)
|
||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
|
||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -913,7 +903,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>${arrayVarName}
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr c64flt.set_array_float
|
||||
jsr floats.set_array_float
|
||||
""")
|
||||
}
|
||||
}
|
||||
@ -921,7 +911,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
|
||||
TargetStorageKind.STACK -> {
|
||||
val floatConst = asmgen.getFloatAsmConst(float)
|
||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
|
||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1117,7 +1107,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
|
||||
dex
|
||||
lda #<$asmArrayvarname
|
||||
ldy #>$asmArrayvarname
|
||||
jsr c64flt.pop_float_to_indexed_var
|
||||
jsr floats.pop_float_to_indexed_var
|
||||
""")
|
||||
else ->
|
||||
throw AssemblyError("weird array type")
|
||||
|
@ -7,11 +7,13 @@ import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
|
||||
import prog8.compiler.toHex
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private val assignmentAsmGen: AssignmentAsmGen,
|
||||
private val exprAsmGen: ExpressionsAsmGen,
|
||||
private val asmgen: AsmGen) {
|
||||
fun translate(assign: AsmAssignment) {
|
||||
require(assign.isAugmentable)
|
||||
@ -104,6 +106,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, value: Expression) {
|
||||
val valueLv = (value as? NumericLiteralValue)?.number
|
||||
val ident = value as? IdentifierReference
|
||||
val memread = value as? DirectMemoryRead
|
||||
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -112,7 +115,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
|
||||
// TODO more specialized code for types such as memory read etc.
|
||||
memread != null -> inplaceModification_byte_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
|
||||
@ -124,18 +127,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
when {
|
||||
valueLv != null -> inplaceModification_word_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_word_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
|
||||
// TODO more specialized code for types such as memory read etc.
|
||||
// value is DirectMemoryRead -> {
|
||||
// println("warning: slow stack evaluation used (8): $name $operator= ${value::class.simpleName} at ${value.position}")
|
||||
// // assignmentAsmGen.translateOtherAssignment(origAssign)
|
||||
// asmgen.translateExpression(value.addressExpression)
|
||||
// asmgen.out("""
|
||||
// jsr prog8_lib.read_byte_from_address_on_stack
|
||||
// sta ...
|
||||
// inx
|
||||
// """)
|
||||
// inplaceModification_word_value_to_variable(name, operator, )
|
||||
// }
|
||||
memread != null -> inplaceModification_word_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
|
||||
@ -147,7 +139,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
when {
|
||||
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble())
|
||||
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident)
|
||||
// TODO more specialized code for types such as memory read etc.
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
inplaceModification_float_value_to_variable(target.asmVarname, operator, value)
|
||||
@ -191,13 +182,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
else -> {
|
||||
println("warning: slow stack evaluation used (1): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO optimize...
|
||||
asmgen.translateExpression(memory.addressExpression)
|
||||
// TODO buggy?:
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
||||
val zp = CompilationTarget.instance.machine.zeropage
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable(zp.SCRATCH_B1.toHex(), DataType.UBYTE, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable(zp.SCRATCH_B1.toHex(), DataType.UBYTE, operator, ident)
|
||||
// TODO more specialized code for types such as memory read etc.
|
||||
memread != null -> inplaceModification_byte_memread_to_variable(zp.SCRATCH_B1.toHex(), DataType.UBYTE, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
if (tryRemoveRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable(zp.SCRATCH_B1.toHex(), DataType.UBYTE, operator, value)
|
||||
@ -451,6 +441,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
"-" -> asmgen.out(" lda $name | sec | sbc P8ESTACK_LO+1,x | sta $name")
|
||||
"*" -> {
|
||||
TODO("var mul byte expr")
|
||||
// check optimizedByteMultiplications
|
||||
// asmgen.out(" jsr prog8_lib.mul_byte")
|
||||
}
|
||||
"/" -> {
|
||||
@ -636,20 +627,77 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $name
|
||||
sta $name""")
|
||||
}
|
||||
"-" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $name
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
sta $name""")
|
||||
// TODO: more operators
|
||||
}
|
||||
else -> {
|
||||
inplaceModification_byte_value_to_variable(name, dt, operator, memread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.out("""
|
||||
clc
|
||||
adc $name
|
||||
sta $name
|
||||
bcc +
|
||||
inc $name+1
|
||||
+""")
|
||||
}
|
||||
"-" -> {
|
||||
exprAsmGen.translateDirectMemReadExpression(memread, false)
|
||||
asmgen.out("""
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda $name
|
||||
sec
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
sta $name
|
||||
bcc +
|
||||
dec $name+1
|
||||
+""")
|
||||
// TODO: more operators
|
||||
}
|
||||
else -> {
|
||||
inplaceModification_word_value_to_variable(name, dt, operator, memread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification_word_litval_to_variable(name: String, dt: DataType, operator: String, value: Int) {
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
// TODO use these + and - optimizations in the expressionAsmGenerator as well.
|
||||
"+" -> {
|
||||
when {
|
||||
value<0x0100 -> asmgen.out("""
|
||||
value==0 -> {}
|
||||
value in 1..0xff -> asmgen.out("""
|
||||
lda $name
|
||||
clc
|
||||
adc #$value
|
||||
sta $name
|
||||
bcc +
|
||||
inc $name+1
|
||||
+ """)
|
||||
+""")
|
||||
value==0x0100 -> asmgen.out(" inc $name+1")
|
||||
value==0x0200 -> asmgen.out(" inc $name+1 | inc $name+1")
|
||||
value==0x0300 -> asmgen.out(" inc $name+1 | inc $name+1 | inc $name+1")
|
||||
@ -666,14 +714,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
"-" -> {
|
||||
when {
|
||||
value<0x0100 -> asmgen.out("""
|
||||
value==0 -> {}
|
||||
value in 1..0xff -> asmgen.out("""
|
||||
lda $name
|
||||
sec
|
||||
sbc #$value
|
||||
sta $name
|
||||
bcs +
|
||||
dec $name+1
|
||||
+ """)
|
||||
+""")
|
||||
value==0x0100 -> asmgen.out(" dec $name+1")
|
||||
value==0x0200 -> asmgen.out(" dec $name+1 | dec $name+1")
|
||||
value==0x0300 -> asmgen.out(" dec $name+1 | dec $name+1 | dec $name+1")
|
||||
@ -693,26 +742,36 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
if(value in asmgen.optimizedWordMultiplications) {
|
||||
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
|
||||
} else {
|
||||
TODO("var uword mul litval $value")
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1""")
|
||||
}
|
||||
} else {
|
||||
if(value.absoluteValue in asmgen.optimizedWordMultiplications) {
|
||||
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
|
||||
} else {
|
||||
// TODO don't use stack here
|
||||
// TODO does this work for signed words?
|
||||
// TODO does this work for signed words? if so the uword/word distinction can be removed altogether
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1""")
|
||||
lda $name
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda $name+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<$value
|
||||
ldy #>$value
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1""")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -827,28 +886,73 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
val valueDt = ident.targetVarDecl(program.namespace)!!.datatype
|
||||
when (valueDt) {
|
||||
in ByteDatatypes -> {
|
||||
// the other variable is a BYTE type so optimize for that TODO does this even occur?
|
||||
// the other variable is a BYTE type so optimize for that
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> asmgen.out("""
|
||||
lda $name
|
||||
clc
|
||||
adc $otherName
|
||||
sta $name
|
||||
bcc +
|
||||
inc $name+1
|
||||
+ """)
|
||||
"-" -> asmgen.out("""
|
||||
lda $name
|
||||
sec
|
||||
sbc $otherName
|
||||
sta $name
|
||||
bcs +
|
||||
dec $name+1
|
||||
+ """)
|
||||
"*" -> TODO("mul word*byte")
|
||||
"/" -> TODO("div word/byte")
|
||||
"%" -> TODO("word remainder byte")
|
||||
"+" -> {
|
||||
if(valueDt==DataType.UBYTE)
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
clc
|
||||
adc $otherName
|
||||
sta $name
|
||||
bcc +
|
||||
inc $name+1
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $otherName
|
||||
bpl +
|
||||
dey ; sign extend
|
||||
+ clc
|
||||
adc $name
|
||||
sta $name
|
||||
tya
|
||||
adc $name+1
|
||||
sta $name+1""")
|
||||
}
|
||||
"-" -> {
|
||||
if(valueDt==DataType.UBYTE)
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
sec
|
||||
sbc $otherName
|
||||
sta $name
|
||||
bcs +
|
||||
dec $name+1
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda $otherName
|
||||
bpl +
|
||||
dey ; sign extend
|
||||
+ sty P8ZP_SCRATCH_B1
|
||||
lda $name
|
||||
sec
|
||||
sbc $otherName
|
||||
sta $name
|
||||
lda $name+1
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
sta $name+1""")
|
||||
}
|
||||
"*" -> {
|
||||
asmgen.out("""
|
||||
lda $otherName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #0
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda $name
|
||||
ldy $name+1
|
||||
jsr math.multiply_words
|
||||
lda math.multiply_words.result
|
||||
sta $name
|
||||
lda math.multiply_words.result+1
|
||||
sta $name+1""")
|
||||
}
|
||||
"/" -> TODO("div (u)wordvar/bytevar")
|
||||
"%" -> TODO("(u)word remainder bytevar")
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
ldy $otherName
|
||||
@ -876,9 +980,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
bne -""")
|
||||
}
|
||||
}
|
||||
"&" -> TODO("bitand word byte")
|
||||
"^" -> TODO("bitxor word byte")
|
||||
"|" -> TODO("bitor word byte")
|
||||
"&" -> TODO("bitand (u)wordvar bytevar")
|
||||
"^" -> TODO("bitxor (u)wordvar bytevar")
|
||||
"|" -> TODO("bitor (u)wordvar bytevar")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -970,28 +1074,60 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
|
||||
when(valueDt) {
|
||||
in ByteDatatypes -> {
|
||||
// the other variable is a BYTE type so optimize for that TODO does this even occur?
|
||||
// the other variable is a BYTE type so optimize for that
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> asmgen.out("""
|
||||
lda $name
|
||||
clc
|
||||
adc P8ESTACK_LO+1,x
|
||||
sta $name
|
||||
bcc +
|
||||
inc $name+1
|
||||
+ """)
|
||||
"-" -> asmgen.out("""
|
||||
lda $name
|
||||
sec
|
||||
sbc P8ESTACK_LO+1,x
|
||||
sta $name
|
||||
bcs +
|
||||
dec $name+1
|
||||
+ """)
|
||||
"*" -> TODO("mul word byte")
|
||||
"/" -> TODO("div word byte")
|
||||
"%" -> TODO("word remainder byte")
|
||||
"+" -> {
|
||||
if(valueDt==DataType.UBYTE)
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
clc
|
||||
adc P8ESTACK_LO+1,x
|
||||
sta $name
|
||||
bcc +
|
||||
inc $name+1
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda P8ESTACK_LO+1,x
|
||||
bpl +
|
||||
dey ; sign extend
|
||||
+ clc
|
||||
adc $name
|
||||
sta $name
|
||||
tya
|
||||
adc $name+1
|
||||
sta $name+1""")
|
||||
}
|
||||
"-" -> {
|
||||
if(valueDt==DataType.UBYTE)
|
||||
asmgen.out("""
|
||||
lda $name
|
||||
sec
|
||||
sbc P8ESTACK_LO+1,x
|
||||
sta $name
|
||||
bcc +
|
||||
dec $name+1
|
||||
+""")
|
||||
else
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda P8ESTACK_LO+1,x
|
||||
bpl +
|
||||
dey ; sign extend
|
||||
+ sty P8ZP_SCRATCH_B1
|
||||
lda $name
|
||||
sec
|
||||
sbc P8ESTACK_LO+1,x
|
||||
sta $name
|
||||
lda $name+1
|
||||
sbc P8ZP_SCRATCH_B1
|
||||
sta $name+1""")
|
||||
}
|
||||
"*" -> TODO("mul (u)word (u)byte")
|
||||
"/" -> TODO("div (u)word (u)byte")
|
||||
"%" -> TODO("(u)word remainder (u)byte")
|
||||
"<<" -> {
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.out("""
|
||||
@ -1030,9 +1166,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
}
|
||||
"&" -> TODO("bitand word byte")
|
||||
"^" -> TODO("bitxor word byte")
|
||||
"|" -> TODO("bitor word byte")
|
||||
"&" -> TODO("bitand (u)word (u)byte")
|
||||
"^" -> TODO("bitxor (u)word (u)byte")
|
||||
"|" -> TODO("bitor (u)word (u)byte")
|
||||
else -> throw AssemblyError("invalid operator for in-place modification $operator")
|
||||
}
|
||||
}
|
||||
@ -1121,43 +1257,43 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
// because the value is evaluated onto the eval stack (=slow).
|
||||
println("warning: slow stack evaluation used (2): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.out(" jsr c64flt.pop_float_fac1")
|
||||
asmgen.out(" jsr floats.pop_float_fac1")
|
||||
asmgen.saveRegister(CpuRegister.X)
|
||||
when (operator) {
|
||||
"**" -> {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.CONUPK
|
||||
jsr c64flt.FPWRT
|
||||
jsr floats.CONUPK
|
||||
jsr floats.FPWRT
|
||||
""")
|
||||
}
|
||||
"+" -> {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.FADD
|
||||
jsr floats.FADD
|
||||
""")
|
||||
}
|
||||
"-" -> {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.FSUB
|
||||
jsr floats.FSUB
|
||||
""")
|
||||
}
|
||||
"*" -> {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.FMULT
|
||||
jsr floats.FMULT
|
||||
""")
|
||||
}
|
||||
"/" -> {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.FDIV
|
||||
jsr floats.FDIV
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
||||
@ -1165,7 +1301,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
ldx #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.MOVMF
|
||||
jsr floats.MOVMF
|
||||
""")
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
}
|
||||
@ -1182,50 +1318,50 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.CONUPK
|
||||
jsr floats.CONUPK
|
||||
lda #<$otherName
|
||||
ldy #>$otherName
|
||||
jsr c64flt.FPWR
|
||||
jsr floats.FPWR
|
||||
""")
|
||||
}
|
||||
"+" -> {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.MOVFM
|
||||
jsr floats.MOVFM
|
||||
lda #<$otherName
|
||||
ldy #>$otherName
|
||||
jsr c64flt.FADD
|
||||
jsr floats.FADD
|
||||
""")
|
||||
}
|
||||
"-" -> {
|
||||
asmgen.out("""
|
||||
lda #<$otherName
|
||||
ldy #>$otherName
|
||||
jsr c64flt.MOVFM
|
||||
jsr floats.MOVFM
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.FSUB
|
||||
jsr floats.FSUB
|
||||
""")
|
||||
}
|
||||
"*" -> {
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.MOVFM
|
||||
jsr floats.MOVFM
|
||||
lda #<$otherName
|
||||
ldy #>$otherName
|
||||
jsr c64flt.FMULT
|
||||
jsr floats.FMULT
|
||||
""")
|
||||
}
|
||||
"/" -> {
|
||||
asmgen.out("""
|
||||
lda #<$otherName
|
||||
ldy #>$otherName
|
||||
jsr c64flt.MOVFM
|
||||
jsr floats.MOVFM
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.FDIV
|
||||
jsr floats.FDIV
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
||||
@ -1234,7 +1370,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
ldx #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.MOVMF
|
||||
jsr floats.MOVMF
|
||||
""")
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
}
|
||||
@ -1247,10 +1383,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.CONUPK
|
||||
jsr floats.CONUPK
|
||||
lda #<$constValueName
|
||||
ldy #>$constValueName
|
||||
jsr c64flt.FPWR
|
||||
jsr floats.FPWR
|
||||
""")
|
||||
}
|
||||
"+" -> {
|
||||
@ -1259,10 +1395,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.MOVFM
|
||||
jsr floats.MOVFM
|
||||
lda #<$constValueName
|
||||
ldy #>$constValueName
|
||||
jsr c64flt.FADD
|
||||
jsr floats.FADD
|
||||
""")
|
||||
}
|
||||
"-" -> {
|
||||
@ -1271,10 +1407,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
lda #<$constValueName
|
||||
ldy #>$constValueName
|
||||
jsr c64flt.MOVFM
|
||||
jsr floats.MOVFM
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.FSUB
|
||||
jsr floats.FSUB
|
||||
""")
|
||||
}
|
||||
"*" -> {
|
||||
@ -1282,10 +1418,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.MOVFM
|
||||
jsr floats.MOVFM
|
||||
lda #<$constValueName
|
||||
ldy #>$constValueName
|
||||
jsr c64flt.FMULT
|
||||
jsr floats.FMULT
|
||||
""")
|
||||
}
|
||||
"/" -> {
|
||||
@ -1294,10 +1430,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
lda #<$constValueName
|
||||
ldy #>$constValueName
|
||||
jsr c64flt.MOVFM
|
||||
jsr floats.MOVFM
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.FDIV
|
||||
jsr floats.FDIV
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
|
||||
@ -1306,7 +1442,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.out("""
|
||||
ldx #<$name
|
||||
ldy #>$name
|
||||
jsr c64flt.MOVMF
|
||||
jsr floats.MOVMF
|
||||
""")
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
}
|
||||
@ -1544,17 +1680,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
DataType.FLOAT -> {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.saveRegister(CpuRegister.X)
|
||||
// simply flip the sign bit in the float
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}
|
||||
jsr c64flt.MOVFM
|
||||
jsr c64flt.NEGOP
|
||||
ldx #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}
|
||||
jsr c64flt.MOVMF
|
||||
lda ${target.asmVarname}+1
|
||||
eor #$80
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> TODO("in-place negate float array")
|
||||
TargetStorageKind.STACK -> TODO("stack float negate")
|
||||
|
@ -32,7 +32,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
override fun getFloatRomConst(number: Double): String? = null // Cx16 has no pulblic ROM float locations
|
||||
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
importer.importLibraryModule(program, "cx16lib")
|
||||
importer.importLibraryModule(program, "syslib")
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
@ -52,6 +52,8 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
override fun isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
}
|
||||
@ -78,13 +80,6 @@ internal object CX16MachineDefinition: IMachineDefinition {
|
||||
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
|
||||
|
||||
|
||||
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
|
||||
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
|
||||
ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
||||
else -> ExitProgramStrategy.SYSTEM_RESET
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
|
||||
|
@ -7,178 +7,6 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
|
||||
|
||||
// First thing to do is replace all constant identifiers with their actual value,
|
||||
// and the array var initializer values and sizes.
|
||||
// This is needed because further constant optimizations depend on those.
|
||||
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
// replace identifiers that refer to const value, with the value itself
|
||||
// if it's a simple type and if it's not a left hand side variable
|
||||
if(identifier.parent is AssignTarget)
|
||||
return noModifications
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if(forloop==null)
|
||||
forloop = identifier.parent.parent as? ForLoop
|
||||
if(forloop!=null && identifier===forloop.loopVar)
|
||||
return noModifications
|
||||
|
||||
val cval = identifier.constValue(program) ?: return noModifications
|
||||
return when (cval.type) {
|
||||
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
|
||||
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
|
||||
else -> noModifications
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||
if(decl.isArray){
|
||||
if(decl.arraysize==null) {
|
||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||
val arrayval = decl.value as? ArrayLiteralValue
|
||||
if(arrayval!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
|
||||
decl
|
||||
))
|
||||
}
|
||||
}
|
||||
else if(decl.arraysize?.constIndex()==null) {
|
||||
val size = decl.arraysize!!.index.constValue(program)
|
||||
if(size!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
size, decl
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when(decl.datatype) {
|
||||
DataType.FLOAT -> {
|
||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(numericLv!=null && numericLv.type==DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.err("ubyte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.err("byte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.err("uword value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.err("word value overflow", numericLv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(rangeExpr==null && litval!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = litval.number.toDouble()
|
||||
if (fillvalue < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", litval.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// nothing to do for this type
|
||||
// this includes strings and structs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR
|
||||
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declValue.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
@ -216,9 +44,24 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
}
|
||||
"~" -> when (subexpr.type) {
|
||||
in IntegerDatatypes -> {
|
||||
DataType.BYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.optimalInteger(subexpr.number.toInt().inv(), subexpr.position),
|
||||
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UBYTE -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.UBYTE, subexpr.number.toInt().inv() and 255, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.UWORD, subexpr.number.toInt().inv() and 65535, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||
|
192
compiler/src/prog8/optimizer/ConstantIdentifierReplacer.kt
Normal file
192
compiler/src/prog8/optimizer/ConstantIdentifierReplacer.kt
Normal file
@ -0,0 +1,192 @@
|
||||
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.ArrayIndex
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
|
||||
// Fix up the literal value's type to match that of the vardecl
|
||||
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val declConstValue = decl.value?.constValue(program)
|
||||
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
|
||||
&& !declConstValue.inferType(program).istype(decl.datatype)) {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
val cast = declConstValue.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Replace all constant identifiers with their actual value,
|
||||
// and the array var initializer values and sizes.
|
||||
// This is needed because further constant optimizations depend on those.
|
||||
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
|
||||
// replace identifiers that refer to const value, with the value itself
|
||||
// if it's a simple type and if it's not a left hand side variable
|
||||
if(identifier.parent is AssignTarget)
|
||||
return noModifications
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if(forloop==null)
|
||||
forloop = identifier.parent.parent as? ForLoop
|
||||
if(forloop!=null && identifier===forloop.loopVar)
|
||||
return noModifications
|
||||
|
||||
val cval = identifier.constValue(program) ?: return noModifications
|
||||
return when (cval.type) {
|
||||
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
|
||||
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
|
||||
else -> noModifications
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
|
||||
if(decl.isArray){
|
||||
if(decl.arraysize==null) {
|
||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||
val arrayval = decl.value as? ArrayLiteralValue
|
||||
if(arrayval!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
|
||||
decl
|
||||
))
|
||||
}
|
||||
}
|
||||
else if(decl.arraysize?.constIndex()==null) {
|
||||
val size = decl.arraysize!!.index.constValue(program)
|
||||
if(size!=null) {
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
size, decl
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when(decl.datatype) {
|
||||
DataType.FLOAT -> {
|
||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val numericLv = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
val newValue = if(eltType in ByteDatatypes) {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
} else {
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
if (rangeExpr==null && numericLv!=null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = numericLv.number.toInt()
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
if(fillvalue !in 0..255)
|
||||
errors.err("ubyte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
if(fillvalue !in -128..127)
|
||||
errors.err("byte value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
if(fillvalue !in 0..65535)
|
||||
errors.err("uword value overflow", numericLv.position)
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
if(fillvalue !in -32768..32767)
|
||||
errors.err("word value overflow", numericLv.position)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array of floats
|
||||
val declArraySize = decl.arraysize?.constIndex()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
|
||||
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(rangeExpr==null && litval!=null) {
|
||||
// arraysize initializer is a single int, and we know the size.
|
||||
val fillvalue = litval.number.toDouble()
|
||||
if (fillvalue < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", litval.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) }.toTypedArray<Expression>()
|
||||
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// nothing to do for this type
|
||||
// this includes strings and structs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
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.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.VarDecl
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
@ -175,28 +179,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed >=0 --> signed ^ $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw >=0 --> msb(signedw) ^ $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0) {
|
||||
@ -297,6 +279,80 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
||||
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
|
||||
// if(decl.type==VarDeclType.VAR ) {
|
||||
// val binExpr = decl.value as? BinaryExpression
|
||||
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
|
||||
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
|
||||
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
|
||||
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
// val assign = Assignment(target, augExpr, binExpr.position)
|
||||
// println("SPLIT VARDECL $decl")
|
||||
// return listOf(
|
||||
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
|
||||
// IAstModification.InsertAfter(decl, assign, parent)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
// return noModifications
|
||||
// }
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
/*
|
||||
|
||||
reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
||||
by attempting to splitting it up into individual simple steps:
|
||||
|
||||
|
||||
X = BinExpr X = LeftExpr
|
||||
<operator> followed by
|
||||
/ \ IF 'X' not used X = BinExpr
|
||||
/ \ IN LEFTEXPR ==> <operator>
|
||||
/ \ / \
|
||||
LeftExpr. RightExpr. / \
|
||||
/ \ / \ X RightExpr.
|
||||
.. .. .. ..
|
||||
|
||||
*/
|
||||
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
|
||||
if (!assignment.isAugmentable) {
|
||||
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
|
||||
val targetExpr = assignment.target.toExpression()
|
||||
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, firstAssign, parent),
|
||||
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO further unraveling of binary expression trees into flat statements.
|
||||
// however this should probably be done in a more generic way to also service
|
||||
// the expressiontrees that are not used in an assignment statement...
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope): Boolean {
|
||||
return when {
|
||||
target.identifier!=null -> target.isInRegularRAM(namespace)
|
||||
target.memoryAddress!=null -> target.isInRegularRAM(namespace)
|
||||
target.arrayindexed!=null -> {
|
||||
val index = target.arrayindexed!!.arrayspec.index
|
||||
if(index is NumericLiteralValue)
|
||||
target.isInRegularRAM(namespace)
|
||||
else
|
||||
false
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCall.target.nameInSource == listOf("lsb")) {
|
||||
val arg = functionCall.args[0]
|
||||
|
@ -5,20 +5,31 @@ import prog8.ast.base.ErrorReporter
|
||||
|
||||
|
||||
internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
val replacer = ConstantIdentifierReplacer(this, errors)
|
||||
replacer.visit(this)
|
||||
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
replacer.applyModifications()
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
val optimizer = ConstantFoldingOptimizer(this)
|
||||
optimizer.visit(this)
|
||||
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
|
||||
optimizer.visit(this)
|
||||
}
|
||||
|
||||
if(errors.isEmpty()) {
|
||||
replacer.visit(this)
|
||||
val replacer = ConstantIdentifierReplacer(this, errors)
|
||||
replacer.visit(this)
|
||||
if (errors.isEmpty()) {
|
||||
replacer.applyModifications()
|
||||
|
||||
valuetypefixer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
valuetypefixer.applyModifications()
|
||||
|
||||
val optimizer = ConstantFoldingOptimizer(this)
|
||||
optimizer.visit(this)
|
||||
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
|
||||
optimizer.visit(this)
|
||||
}
|
||||
|
||||
if (errors.isEmpty()) {
|
||||
replacer.visit(this)
|
||||
replacer.applyModifications()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,11 +49,6 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val linesToRemove = deduplicateAssignments(subroutine.statements)
|
||||
if(linesToRemove.isNotEmpty()) {
|
||||
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
|
||||
}
|
||||
|
||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
return listOf(IAstModification.Remove(subroutine, parent))
|
||||
@ -62,11 +57,6 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||
return linesToRemove.reversed().map { IAstModification.Remove(scope.statements[it], scope) }
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
||||
@ -249,11 +239,11 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
|
||||
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = untilLoop.untilCondition.constValue(program)
|
||||
val constvalue = untilLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if(constvalue.asBooleanValue) {
|
||||
// always true -> keep only the statement block (if there are no break statements)
|
||||
errors.warn("condition is always true", untilLoop.untilCondition.position)
|
||||
errors.warn("condition is always true", untilLoop.condition.position)
|
||||
if(!hasBreak(untilLoop.body))
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
|
||||
} else {
|
||||
@ -395,21 +385,21 @@ internal class StatementOptimizer(private val program: Program,
|
||||
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (cv != null && assignment.target isSameAs bexpr.left) {
|
||||
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (rightCv != null && assignment.target isSameAs bexpr.left) {
|
||||
// assignments of the form: X = X <operator> <expr>
|
||||
// remove assignments that have no effect (such as X=X+0)
|
||||
// optimize/rewrite some other expressions
|
||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
if (cv == 0.0) {
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..4.0) {
|
||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
|
||||
val incs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
repeat(rightCv.toInt()) {
|
||||
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
||||
@ -417,62 +407,38 @@ internal class StatementOptimizer(private val program: Program,
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if (cv == 0.0) {
|
||||
if (rightCv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..4.0) {
|
||||
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
|
||||
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
repeat(rightCv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"/" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"**" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"|" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"^" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"<<" -> {
|
||||
if (cv == 0.0)
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
}
|
||||
">>" -> {
|
||||
if (cv == 0.0)
|
||||
if (rightCv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
|
||||
// removes 'duplicate' assignments that assign the isSameAs target
|
||||
val linesToRemove = mutableListOf<Int>()
|
||||
var previousAssignmentLine: Int? = null
|
||||
for (i in statements.indices) {
|
||||
val stmt = statements[i] as? Assignment
|
||||
if (stmt != null && stmt.value is NumericLiteralValue) {
|
||||
if (previousAssignmentLine == null) {
|
||||
previousAssignmentLine = i
|
||||
continue
|
||||
} else {
|
||||
val prev = statements[previousAssignmentLine] as Assignment
|
||||
if (prev.target.isSameAs(stmt.target, program)) {
|
||||
// get rid of the previous assignment, if the target is not MEMORY
|
||||
if (prev.target.isNotMemory(program.namespace))
|
||||
linesToRemove.add(previousAssignmentLine)
|
||||
}
|
||||
previousAssignmentLine = i
|
||||
}
|
||||
} else
|
||||
previousAssignmentLine = null
|
||||
}
|
||||
return linesToRemove
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun hasBreak(scope: INameScope): Boolean {
|
||||
|
@ -8,7 +8,8 @@ import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
internal class UnusedCodeRemover(private val errors: ErrorReporter): AstWalker() {
|
||||
|
||||
internal class UnusedCodeRemover(private val program: Program, private val errors: ErrorReporter): AstWalker() {
|
||||
|
||||
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
|
||||
val callgraph = CallGraph(program)
|
||||
@ -66,4 +67,35 @@ internal class UnusedCodeRemover(private val errors: ErrorReporter): AstWalker()
|
||||
else -> errors.warn("unreachable code", next.position)
|
||||
}
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(scope.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, scope) }
|
||||
}
|
||||
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(block.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
|
||||
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
|
||||
// removes 'duplicate' assignments that assign the same target directly after another
|
||||
val linesToRemove = mutableListOf<Assignment>()
|
||||
|
||||
for (stmtPairs in statements.windowed(2, step = 1)) {
|
||||
val assign1 = stmtPairs[0] as? Assignment
|
||||
val assign2 = stmtPairs[1] as? Assignment
|
||||
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
|
||||
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace))
|
||||
linesToRemove.add(assign1)
|
||||
}
|
||||
}
|
||||
|
||||
return linesToRemove
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.base.checkImportedValid
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.DirectiveArg
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.pathFrom
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Files
|
||||
@ -19,14 +20,6 @@ import java.nio.file.Paths
|
||||
internal class ParsingFailedError(override var message: String) : Exception(message)
|
||||
|
||||
|
||||
private class LexerErrorListener: BaseErrorListener() {
|
||||
var numberOfErrors: Int = 0
|
||||
override fun syntaxError(p0: Recognizer<*, *>?, p1: Any?, p2: Int, p3: Int, p4: String?, p5: RecognitionException?) {
|
||||
numberOfErrors++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
|
||||
|
||||
|
||||
@ -60,13 +53,28 @@ internal class ModuleImporter {
|
||||
return executeImportDirective(program, import, Paths.get(""))
|
||||
}
|
||||
|
||||
private class MyErrorListener: ConsoleErrorListener() {
|
||||
var numberOfErrors: Int = 0
|
||||
override fun syntaxError(recognizer: Recognizer<*, *>?, offendingSymbol: Any?, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException?) {
|
||||
numberOfErrors++
|
||||
when (recognizer) {
|
||||
is CustomLexer -> System.err.println("${recognizer.modulePath}:$line:$charPositionInLine: $msg")
|
||||
is prog8Parser -> System.err.println("${recognizer.inputStream.sourceName}:$line:$charPositionInLine: $msg")
|
||||
else -> System.err.println("$line:$charPositionInLine $msg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.removeErrorListeners()
|
||||
val lexerErrors = MyErrorListener()
|
||||
lexer.addErrorListener(lexerErrors)
|
||||
val tokens = CommentHandlingTokenStream(lexer)
|
||||
val parser = prog8Parser(tokens)
|
||||
parser.removeErrorListeners()
|
||||
parser.addErrorListener(MyErrorListener())
|
||||
val parseTree = parser.module()
|
||||
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
|
||||
if(numberOfErrors > 0)
|
||||
@ -123,13 +131,14 @@ internal class ModuleImporter {
|
||||
if(existing!=null)
|
||||
return null
|
||||
|
||||
val resource = tryGetEmbeddedResource("$moduleName.p8")
|
||||
val rsc = tryGetEmbeddedResource("$moduleName.p8")
|
||||
val importedModule =
|
||||
if(resource!=null) {
|
||||
if(rsc!=null) {
|
||||
// load the module from the embedded resource
|
||||
val (resource, resourcePath) = rsc
|
||||
resource.use {
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"), true)
|
||||
}
|
||||
} else {
|
||||
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
|
||||
@ -140,7 +149,18 @@ internal class ModuleImporter {
|
||||
return importedModule
|
||||
}
|
||||
|
||||
private fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
private fun tryGetEmbeddedResource(name: String): Pair<InputStream, String>? {
|
||||
val target = CompilationTarget.instance.name
|
||||
val targetSpecificPath = "/prog8lib/$target/$name"
|
||||
val targetSpecificResource = object{}.javaClass.getResourceAsStream(targetSpecificPath)
|
||||
if(targetSpecificResource!=null)
|
||||
return Pair(targetSpecificResource, targetSpecificPath)
|
||||
|
||||
val generalPath = "/prog8lib/$name"
|
||||
val generalResource = object{}.javaClass.getResourceAsStream(generalPath)
|
||||
if(generalResource!=null)
|
||||
return Pair(generalResource, generalPath)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -5,18 +5,21 @@ import org.hamcrest.Matchers.closeTo
|
||||
import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.StringLiteralValue
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.C64Target
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import java.io.CharConversionException
|
||||
import java.nio.file.Path
|
||||
import kotlin.test.*
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
@ -129,7 +132,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testNames() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, "c64"))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
zp.allocate("", DataType.UBYTE, null, errors)
|
||||
@ -142,37 +145,37 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testZpFloatEnable() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, "c64"))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertFailsWith<CompilerException> {
|
||||
zp.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, "c64"))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false))
|
||||
assertFailsWith<CompilerException> {
|
||||
zp2.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, "c64"))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
|
||||
zp3.allocate("", DataType.FLOAT, null, errors)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpModesWithFloats() {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, "c64"))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, "c64"))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, "c64"))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, "c64"))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, "c64"))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, "c64"))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
|
||||
assertFailsWith<CompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, "c64"))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false))
|
||||
}
|
||||
assertFailsWith<CompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, "c64"))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpDontuse() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, "c64"))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false))
|
||||
println(zp.free)
|
||||
assertEquals(0, zp.available())
|
||||
assertFailsWith<CompilerException> {
|
||||
@ -182,19 +185,19 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testFreeSpaces() {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, "c64"))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(16, zp1.available())
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, "c64"))
|
||||
assertEquals(91, zp2.available())
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, "c64"))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
|
||||
assertEquals(89, zp2.available())
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
|
||||
assertEquals(125, zp3.available())
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, "c64"))
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertEquals(238, zp4.available())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedSpace() {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, "c64"))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertEquals(238, zp1.available())
|
||||
assertTrue(50 in zp1.free)
|
||||
assertTrue(100 in zp1.free)
|
||||
@ -203,7 +206,7 @@ class TestC64Zeropage {
|
||||
assertTrue(200 in zp1.free)
|
||||
assertTrue(255 in zp1.free)
|
||||
assertTrue(199 in zp1.free)
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, "c64"))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false))
|
||||
assertEquals(139, zp2.available())
|
||||
assertFalse(50 in zp2.free)
|
||||
assertFalse(100 in zp2.free)
|
||||
@ -216,7 +219,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testBasicsafeAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, "c64"))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(16, zp.available())
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
@ -239,7 +242,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testFullAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, "c64"))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
|
||||
assertEquals(238, zp.available())
|
||||
val loc = zp.allocate("", DataType.UWORD, null, errors)
|
||||
assertTrue(loc > 3)
|
||||
@ -269,7 +272,7 @@ class TestC64Zeropage {
|
||||
|
||||
@Test
|
||||
fun testEfficientAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, "c64"))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
|
||||
assertEquals(16, zp.available())
|
||||
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
|
||||
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
|
||||
@ -379,3 +382,169 @@ class TestPetscii {
|
||||
assertFalse(abc!=abc)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestMemory {
|
||||
@Test
|
||||
fun testInValidRamC64_memory_addresses() {
|
||||
CompilationTarget.instance = C64Target
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertTrue(target.isInRegularRAM(scope))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memory_addresses() {
|
||||
CompilationTarget.instance = C64Target
|
||||
|
||||
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
|
||||
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
|
||||
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
|
||||
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_identifiers() {
|
||||
CompilationTarget.instance = C64Target
|
||||
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
private fun createTestProgramForMemoryRefViaVar(address: Int, vartype: VarDeclType): AssignTarget {
|
||||
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
return target
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memory_expression() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
|
||||
val scope = AnonymousScope(mutableListOf(), Position.DUMMY)
|
||||
assertFalse(target.isInRegularRAM(scope))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_variable() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_memmap_variable() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotInValidRamC64_memmap_variable() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0xd020
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testInValidRamC64_array_memmapped() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0x1000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertTrue(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotValidRamC64_array_memmapped() {
|
||||
CompilationTarget.instance = C64Target
|
||||
val address = 0xe000
|
||||
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
|
||||
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
|
||||
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
|
||||
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
|
||||
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
|
||||
subroutine.linkParents(ParentSentinel)
|
||||
assertFalse(target.isInRegularRAM(target.definingScope()))
|
||||
}
|
||||
}
|
||||
|
BIN
docs/source/_static/cobra3d.png
Normal file
BIN
docs/source/_static/cobra3d.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
@ -149,10 +149,10 @@ If your running program hits one of the breakpoints, Vice will halt execution an
|
||||
Troubleshooting
|
||||
---------------
|
||||
|
||||
Getting an assembler error about undefined symbols such as ``not defined 'c64flt'``?
|
||||
This happens when your program uses floating point values, and you forgot to import ``c64flt`` library.
|
||||
Getting an assembler error about undefined symbols such as ``not defined 'floats'``?
|
||||
This happens when your program uses floating point values, and you forgot to import ``floats`` library.
|
||||
If you use floating points, the compiler needs routines from that library.
|
||||
Fix it by adding an ``%import c64flt``.
|
||||
Fix it by adding an ``%import floats``.
|
||||
|
||||
|
||||
Examples
|
||||
|
@ -38,21 +38,22 @@ This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/license
|
||||
:alt: Fully playable tetris clone
|
||||
|
||||
|
||||
Code examples
|
||||
-------------
|
||||
Code example
|
||||
------------
|
||||
|
||||
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
%import c64textio
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
ubyte[256] sieve
|
||||
ubyte candidate_prime = 2
|
||||
ubyte candidate_prime = 2 ; is increased in the loop
|
||||
|
||||
sub start() {
|
||||
memset(sieve, 256, false) ; clear the sieve
|
||||
; clear the sieve, to reset starting situation on subsequent runs
|
||||
memset(sieve, 256, false)
|
||||
; calculate primes
|
||||
txt.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
repeat {
|
||||
@ -63,18 +64,19 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
txt.print(", ")
|
||||
amount++
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
txt.chrout('\n')
|
||||
txt.print("number of primes (expected 54): ")
|
||||
txt.print_ub(amount)
|
||||
c64.CHROUT('\n')
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
sub find_next_prime() -> ubyte {
|
||||
while sieve[candidate_prime] {
|
||||
candidate_prime++
|
||||
if candidate_prime==0
|
||||
return 0 ; we wrapped; no more primes available
|
||||
return 0 ; we wrapped; no more primes available in the sieve
|
||||
}
|
||||
|
||||
; found next one, mark the multiples and return it.
|
||||
sieve[candidate_prime] = true
|
||||
uword multiple = candidate_prime
|
||||
@ -88,7 +90,6 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
}
|
||||
|
||||
|
||||
|
||||
when compiled an ran on a C-64 you get this:
|
||||
|
||||
.. image:: _static/primes_example.png
|
||||
@ -96,43 +97,6 @@ when compiled an ran on a C-64 you get this:
|
||||
:alt: result when run on C-64
|
||||
|
||||
|
||||
The following programs shows a use of the high level ``struct`` type::
|
||||
|
||||
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
struct Color {
|
||||
ubyte red
|
||||
ubyte green
|
||||
ubyte blue
|
||||
}
|
||||
|
||||
sub start() {
|
||||
|
||||
Color purple = [255, 0, 255]
|
||||
|
||||
Color other
|
||||
|
||||
other = purple
|
||||
other.red /= 2
|
||||
other.green = 10 + other.green / 2
|
||||
other.blue = 99
|
||||
|
||||
txt.print_ub(other.red)
|
||||
c64.CHROUT(',')
|
||||
txt.print_ub(other.green)
|
||||
c64.CHROUT(',')
|
||||
txt.print_ub(other.blue)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
when compiled and ran, it prints ``127,10,99`` on the screen.
|
||||
|
||||
|
||||
Design principles and features
|
||||
------------------------------
|
||||
@ -162,6 +126,7 @@ Design principles and features
|
||||
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``, ``substr``, ``sort`` and ``reverse`` (and others)
|
||||
- Assembling the generated code into a program wil be done by an external cross-assembler tool.
|
||||
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
|
||||
|
||||
.. _requirements:
|
||||
|
@ -226,7 +226,7 @@ This is because routines in the C-64 BASIC and KERNAL ROMs are used for that.
|
||||
So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM)
|
||||
are banked in.
|
||||
|
||||
Also your code needs to import the ``c64flt`` library to enable floating point support
|
||||
Also your code needs to import the ``floats`` library to enable floating point support
|
||||
in the compiler, and to gain access to the floating point routines.
|
||||
(this library contains the directive to enable floating points, you don't have
|
||||
to worry about this yourself)
|
||||
@ -236,12 +236,15 @@ The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (ne
|
||||
|
||||
Arrays
|
||||
^^^^^^
|
||||
Array types are also supported. They can be made of bytes, words or floats::
|
||||
Array types are also supported. They can be made of bytes, words or floats, strings, and other arrays
|
||||
(although the usefulness of the latter is very limited for now)::
|
||||
|
||||
byte[10] array ; array of 10 bytes, initially set to 0
|
||||
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
|
||||
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
||||
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to uword)
|
||||
uword[] others = [names, array] ; array of pointers/addresses to other arrays
|
||||
|
||||
value = array[3] ; the fourth value in the array (index is 0-based)
|
||||
char = string[4] ; the fifth character (=byte) in the string
|
||||
@ -293,6 +296,12 @@ large enough to contain the new value::
|
||||
string1 = "new value"
|
||||
|
||||
|
||||
.. info::
|
||||
Strings and uwords (=memory address) can often be interchanged.
|
||||
An array of strings is actually an array of uwords where every element is the memory
|
||||
address of the string. You can pass a memory address to assembly functions
|
||||
that require a string as an argument.
|
||||
|
||||
.. caution::
|
||||
It's probably best to avoid changing strings after they've been created. This
|
||||
includes changing certain letters by index, or by assigning a new value, or by
|
||||
@ -327,7 +336,7 @@ and then create a variable with it::
|
||||
ubyte blue
|
||||
}
|
||||
|
||||
Color rgb = {255,122,0} ; note the curly braces here instead of brackets
|
||||
Color rgb = [255,122,0] ; note that struct initializer value is same as an array
|
||||
Color another ; the init value is optional, like arrays
|
||||
|
||||
another = rgb ; assign all of the values of rgb to another
|
||||
@ -578,25 +587,24 @@ within parentheses will be evaluated first. So ``(4 + 8) * 2`` is 24 and not 20,
|
||||
and ``(true or false) and false`` is false instead of true.
|
||||
|
||||
.. attention::
|
||||
**calculations keep their datatype:**
|
||||
**calculations keep their datatype even if the target variable is larger:**
|
||||
When you do calculations on a BYTE type, the result will remain a BYTE.
|
||||
When you do calculations on a WORD type, the result will remain a WORD.
|
||||
For instance::
|
||||
|
||||
byte b = 44
|
||||
word w = b*55 ; the result will be 116! (even though the target variable is a word)
|
||||
w *= 999 ; the result will be -15188 (the multiplication stays within a word)
|
||||
w *= 999 ; the result will be -15188 (the multiplication stays within a word, but overflows)
|
||||
|
||||
The compiler will NOT give a warning about this! It's doing this for
|
||||
*The compiler does NOT warn about this!* It's doing this for
|
||||
performance reasons - so you won't get sudden 16 bit (or even float)
|
||||
calculations where you needed only simple fast byte arithmetic.
|
||||
If you do need the extended resulting value, cast at least one of the
|
||||
operands of an operator to the larger datatype. For example::
|
||||
operands explicitly to the larger datatype. For example::
|
||||
|
||||
byte b = 44
|
||||
word w = b*55.w ; the result will be 2420
|
||||
w = (b as word)*55 ; same result
|
||||
|
||||
w = (b as word)*55
|
||||
w = b*(55 as word)
|
||||
|
||||
|
||||
|
||||
@ -719,7 +727,7 @@ sort(array)
|
||||
floating point values.
|
||||
|
||||
reverse(array)
|
||||
Reverse the values in the array (in-place). Supports all data types including floats.
|
||||
Reverse the values in the array (in-place).
|
||||
Can be used after sort() to sort an array in descending order.
|
||||
|
||||
len(x)
|
||||
@ -803,7 +811,7 @@ memset(address, numbytes, bytevalue)
|
||||
Efficiently set a part of memory to the given (u)byte value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
Note that for clearing the character screen, very fast specialized subroutines are
|
||||
available in the ``screen`` block (part of the ``c64textio`` or ``cx16textio`` modules)
|
||||
available in the ``txt`` block (part of the ``textio`` module)
|
||||
|
||||
memsetw(address, numwords, wordvalue)
|
||||
Efficiently set a part of memory to the given (u)word value.
|
||||
|
@ -36,9 +36,8 @@ Directives
|
||||
.. data:: %target <target>
|
||||
|
||||
Level: module.
|
||||
Global setting, selects a compilation target from within the source file.
|
||||
The default compilation target is "c64" which targets the Commodore-64 machine.
|
||||
You can also omit this and use the ``-target`` command line option.
|
||||
Global setting, specifies that this module can only work for the given compiler target.
|
||||
If compiled with a different target, compilation is aborted with an error message.
|
||||
|
||||
|
||||
.. data:: %output <type>
|
||||
@ -68,7 +67,8 @@ Directives
|
||||
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
|
||||
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
|
||||
including default IRQs during normal system operation.
|
||||
When the program exits, a system reset is performed (because BASIC will be in a corrupt state).
|
||||
It's not possible to return cleanly to BASIC when the program exits. The only choice is
|
||||
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
|
||||
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
|
||||
are required to perform floating point operations (from the BASIC kernel). No clean exit is possible.
|
||||
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
|
||||
@ -79,9 +79,10 @@ Directives
|
||||
except the few addresses mentioned above that are used by the system's IRQ routine.
|
||||
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
|
||||
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
|
||||
This option makes programs smaller and faster because even more variables can
|
||||
be stored in the ZP (which allows for more efficient assembly code).
|
||||
It's not possible to return cleanly to BASIC when the program exits. The only choice is
|
||||
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
|
||||
- style ``dontuse`` -- don't use *any* location in the zeropage.
|
||||
|
||||
Also read :ref:`zeropage`.
|
||||
@ -118,33 +119,38 @@ Directives
|
||||
|
||||
Level: module, block.
|
||||
Sets special compiler options.
|
||||
For a module option, only the ``enable_floats`` option is recognised, which will tell the compiler
|
||||
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
|
||||
Otherwise, floating point support is not enabled.
|
||||
When used in a block with the ``force_output`` option, it will force the block to be outputted
|
||||
in the final program. Can be useful to make sure some
|
||||
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
|
||||
|
||||
- For a module option, there is ``enable_floats``, which will tell the compiler
|
||||
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
|
||||
Otherwise, floating point support is not enabled.
|
||||
- There's also ``no_sysinit`` which cause the resulting program to *not* include
|
||||
the system re-initialization logic of clearing the screen, resetting I/O config etc. You'll have to
|
||||
take care of that yourself. The program will just start running from whatever state the machine is in when the
|
||||
program was launched.
|
||||
- When used in a block with the ``force_output`` option, it will force the block to be outputted
|
||||
in the final program. Can be useful to make sure some
|
||||
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
|
||||
|
||||
|
||||
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
|
||||
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
|
||||
The optional offset and length can be used to select a particular piece of the file.
|
||||
The file is located relative to the current working directory!
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
|
||||
The optional offset and length can be used to select a particular piece of the file.
|
||||
The file is located relative to the current working directory!
|
||||
|
||||
.. data:: %asminclude "<filename>", "scopelabel"
|
||||
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as raw assembly source text at this point,
|
||||
prog8 will not process this at all, with one exception: the labels.
|
||||
The scopelabel argument will be used as a prefix to access the labels from the included source code,
|
||||
otherwise you would risk symbol redefinitions or duplications.
|
||||
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
|
||||
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
|
||||
if the file can't be found there it is searched relative to the current directory.
|
||||
Level: block.
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as raw assembly source text at this point,
|
||||
prog8 will not process this at all, with one exception: the labels.
|
||||
The scopelabel argument will be used as a prefix to access the labels from the included source code,
|
||||
otherwise you would risk symbol redefinitions or duplications.
|
||||
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
|
||||
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
|
||||
if the file can't be found there it is searched relative to the current directory.
|
||||
|
||||
.. data:: %breakpoint
|
||||
|
||||
@ -275,6 +281,7 @@ type identifier type storage size example var declara
|
||||
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
|
||||
``uword[]`` unsigned word array depends on value ``uword[] myvar = [1, 2, 3, 4]``
|
||||
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
|
||||
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]``
|
||||
``str`` string (petscii) varies ``str myvar = "hello."``
|
||||
implicitly terminated by a 0-byte
|
||||
=============== ======================= ================= =========================================
|
||||
@ -297,7 +304,8 @@ of something with an operand starting with 1 or 0, you'll have to add a space in
|
||||
- When an integer value ranges from 256..65535 the compiler sees it as a ``uword``. For -32768..32767 it's a ``word``.
|
||||
- When a hex number has 3 or 4 digits, for example ``$0004``, it is seen as a ``word`` otherwise as a ``byte``.
|
||||
- When a binary number has 9 to 16 digits, for example ``%1100110011``, it is seen as a ``word`` otherwise as a ``byte``.
|
||||
- You can force a byte value into a word value by adding the ``.w`` datatype suffix to the number: ``$2a.w`` is equivalent to ``$002a``.
|
||||
- If the number fits in a byte but you really require it as a word value, you'll have to explicitly cast it: ``60 as uword``
|
||||
or you can use the full word hexadecimal notation ``$003c``.
|
||||
|
||||
|
||||
Data type conversion
|
||||
@ -391,7 +399,7 @@ After defining a struct you can use the name of the struct as a data type to dec
|
||||
|
||||
Struct variables can be assigned a struct literal value (also in their declaration as initial value)::
|
||||
|
||||
Color rgb = {255, 100, 0} ; curly braces instead of brackets
|
||||
Color rgb = [255, 100, 0] ; note that the value is an array
|
||||
|
||||
|
||||
Operators
|
||||
|
@ -9,13 +9,16 @@ Prog8 targets the following hardware:
|
||||
- optional use of memory mapped I/O registers
|
||||
- optional use of system ROM routines
|
||||
|
||||
Currently there are two machines that are supported as compiler target (via the ``-target`` compiler argument):
|
||||
Currently there are two machines that are supported as compiler target (selectable via the ``-target`` compiler argument):
|
||||
|
||||
- 'c64': the well-known Commodore-64, premium support
|
||||
- 'cx16': the `CommanderX16 <https://www.commanderx16.com/>`_ a project from the 8-Bit Guy. Support for this is still experimental.
|
||||
|
||||
This chapter explains the relevant system details of these machines.
|
||||
|
||||
.. note::
|
||||
If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
|
||||
|
||||
|
||||
Memory Model
|
||||
============
|
||||
|
@ -3,32 +3,27 @@ TODO
|
||||
====
|
||||
|
||||
- get rid of all other TODO's in the code ;-)
|
||||
- line-circle-gfx examples are now a few hundred bytes larger than before. Why is that, can it be fixed?
|
||||
- compiler errors and warnings in standard format so the IDE shows them as clickable links; ./test.asm:2578:3: blablabla
|
||||
- further optimize assignment codegeneration
|
||||
- auto select correct library to import based on target, instead of having c64- and cx16- prefix variants
|
||||
- make it possible for array literals to not only contain compile time constants?
|
||||
- implement @stack for asmsub parameters
|
||||
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
|
||||
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
|
||||
- aliases for imported symbols for example perhaps '%alias print = c64scr.print' ?
|
||||
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
|
||||
|
||||
- use VIC banking to move up the graphics bitmap memory location. Don't move it under the ROM though as that would require IRQ disabling and memory bank swapping for every bitmap manipulation
|
||||
- add some primitives/subroutines/examples for using custom char sets, copying the default charset.
|
||||
- recursive subroutines? via %option recursive, allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
|
||||
|
||||
More optimizations
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Add more compiler optimizations to the existing ones.
|
||||
|
||||
- more targeted optimizations for assigment asm code, such as the following:
|
||||
- further optimize assignment codegeneration, such as the following:
|
||||
- binexpr splitting (beware self-referencing expressions and asm code ballooning though)
|
||||
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
|
||||
- remove unreachable code after an exit(), return or goto
|
||||
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
|
||||
the program will then rely solely on the values as they are in memory at the time of program startup.
|
||||
- Also some library routines and code patterns could perhaps be optimized further
|
||||
- can the parameter passing to subroutines be optimized to avoid copying?
|
||||
- can such parameter passing to subroutines be optimized to avoid copying?
|
||||
- more optimizations on the language AST level
|
||||
- more optimizations on the final assembly source level
|
||||
- note: abandoned subroutine inlining because of problems referencing non-local stuff. Can't move everything around.
|
||||
- note: subroutine inlining is abandoned because of problems referencing non-local stuff. Can't move everything around.
|
||||
|
||||
|
||||
Eval stack redesign? (lot of work)
|
||||
|
@ -1,5 +1,5 @@
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%import floats
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64textio
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%import floats
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -93,11 +93,11 @@ main {
|
||||
txt.print("err! ")
|
||||
|
||||
txt.print("float ")
|
||||
c64flt.print_f(a1)
|
||||
floats.print_f(a1)
|
||||
txt.print(" / ")
|
||||
c64flt.print_f(a2)
|
||||
floats.print_f(a2)
|
||||
txt.print(" = ")
|
||||
c64flt.print_f(r)
|
||||
floats.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%import floats
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -101,11 +101,11 @@ main {
|
||||
txt.print("err! ")
|
||||
|
||||
txt.print("float ")
|
||||
c64flt.print_f(a1)
|
||||
floats.print_f(a1)
|
||||
txt.print(" - ")
|
||||
c64flt.print_f(a2)
|
||||
floats.print_f(a2)
|
||||
txt.print(" = ")
|
||||
c64flt.print_f(r)
|
||||
floats.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%import floats
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -95,11 +95,11 @@ main {
|
||||
txt.print("err! ")
|
||||
|
||||
txt.print("float ")
|
||||
c64flt.print_f(a1)
|
||||
floats.print_f(a1)
|
||||
txt.print(" * ")
|
||||
c64flt.print_f(a2)
|
||||
floats.print_f(a2)
|
||||
txt.print(" = ")
|
||||
c64flt.print_f(r)
|
||||
floats.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%import floats
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -99,11 +99,11 @@ main {
|
||||
txt.print("err! ")
|
||||
|
||||
txt.print("float ")
|
||||
c64flt.print_f(a1)
|
||||
floats.print_f(a1)
|
||||
txt.print(" + ")
|
||||
c64flt.print_f(a2)
|
||||
floats.print_f(a2)
|
||||
txt.print(" = ")
|
||||
c64flt.print_f(r)
|
||||
floats.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%import floats
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -132,9 +132,9 @@ main {
|
||||
else
|
||||
txt.print("err! ")
|
||||
txt.print(" float ")
|
||||
c64flt.print_f(value)
|
||||
floats.print_f(value)
|
||||
c64.CHROUT(',')
|
||||
c64flt.print_f(expected)
|
||||
floats.print_f(expected)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64textio
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
@ -1,5 +1,5 @@
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%import floats
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
315
examples/arithmetic/wordbyte.p8
Normal file
315
examples/arithmetic/wordbyte.p8
Normal file
@ -0,0 +1,315 @@
|
||||
%import textio
|
||||
%import syslib
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
repeat 25 {
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
ubyte ub
|
||||
byte bb
|
||||
uword uwsum
|
||||
word wsum
|
||||
|
||||
uwsum = 50000
|
||||
ub=50
|
||||
uwsum += ub
|
||||
ub=250
|
||||
uwsum += ub
|
||||
|
||||
if uwsum==50300
|
||||
txt.print("1 ok\n")
|
||||
else {
|
||||
txt.print("1 fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
bb = 100
|
||||
wsum += bb
|
||||
bb = -50
|
||||
wsum += bb
|
||||
|
||||
if wsum==-29950
|
||||
txt.print("2 ok\n")
|
||||
else {
|
||||
txt.print("2 fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
uwsum = 50000
|
||||
ub=50
|
||||
uwsum -= ub
|
||||
ub=250
|
||||
uwsum -= ub
|
||||
|
||||
if uwsum==49700
|
||||
txt.print("3 ok\n")
|
||||
else {
|
||||
txt.print("3 fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
wsum = -30000
|
||||
bb = 100
|
||||
wsum -= bb
|
||||
bb = -50
|
||||
wsum -= bb
|
||||
|
||||
if wsum==-30050
|
||||
txt.print("4 ok\n")
|
||||
else
|
||||
txt.print("4 fail\n")
|
||||
|
||||
|
||||
|
||||
uwsum = 50000
|
||||
bb=50
|
||||
uwsum += bb as uword
|
||||
bb=-100
|
||||
uwsum += bb as uword
|
||||
|
||||
if uwsum==49950
|
||||
txt.print("5 ok\n")
|
||||
else
|
||||
txt.print("5 fail\n")
|
||||
|
||||
uwsum = 50000
|
||||
bb=50
|
||||
uwsum -= bb as uword
|
||||
bb=100
|
||||
uwsum -= bb as uword
|
||||
|
||||
if uwsum==49850
|
||||
txt.print("6 ok\n")
|
||||
else {
|
||||
txt.print("6 fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
ub = 50
|
||||
wsum += ub
|
||||
ub = 250
|
||||
wsum += ub
|
||||
|
||||
if wsum==-29700
|
||||
txt.print("7 ok\n")
|
||||
else {
|
||||
txt.print("7 fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
ub = 50
|
||||
wsum -= ub
|
||||
ub = 250
|
||||
wsum -= ub
|
||||
|
||||
if wsum==-30300
|
||||
txt.print("8 ok\n")
|
||||
else {
|
||||
txt.print("8 fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
txt.chrout('\n')
|
||||
|
||||
|
||||
|
||||
uwsum = 50000
|
||||
ub=0
|
||||
uwsum += (50+ub)
|
||||
uwsum += (250+ub)
|
||||
|
||||
if uwsum==50300
|
||||
txt.print("1b ok\n")
|
||||
else {
|
||||
txt.print("1b fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
bb = 0
|
||||
wsum = -30000
|
||||
wsum += (100+bb)
|
||||
wsum += (-50+bb)
|
||||
|
||||
if wsum==-29950
|
||||
txt.print("2b ok\n")
|
||||
else {
|
||||
txt.print("2b fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
uwsum = 50000
|
||||
uwsum -= (50+ub)
|
||||
uwsum -= (250+ub)
|
||||
|
||||
if uwsum==49700
|
||||
txt.print("3b ok\n")
|
||||
else {
|
||||
txt.print("3b fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
wsum = -30000
|
||||
wsum -= (100+bb)
|
||||
wsum -= (-50+bb)
|
||||
|
||||
if wsum==-30050
|
||||
txt.print("4b ok\n")
|
||||
else
|
||||
txt.print("4b fail\n")
|
||||
|
||||
|
||||
uwsum = 50000
|
||||
uwsum += (50+bb) as uword
|
||||
uwsum += (-100+bb) as uword
|
||||
|
||||
if uwsum==49950
|
||||
txt.print("5b ok\n")
|
||||
else
|
||||
txt.print("5b fail\n")
|
||||
|
||||
uwsum = 50000
|
||||
uwsum -= (50+bb) as uword
|
||||
uwsum -= (100+bb) as uword
|
||||
|
||||
if uwsum==49850
|
||||
txt.print("6b ok\n")
|
||||
else {
|
||||
txt.print("6b fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
wsum += (50+ub)
|
||||
wsum += (250+ub)
|
||||
|
||||
if wsum==-29700
|
||||
txt.print("7b ok\n")
|
||||
else {
|
||||
txt.print("7b fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
wsum -= (50+ub)
|
||||
wsum -= (250+ub)
|
||||
|
||||
if wsum==-30300
|
||||
txt.print("8b ok\n")
|
||||
else {
|
||||
txt.print("8b fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
txt.chrout('\n')
|
||||
|
||||
|
||||
|
||||
uwsum = 50000
|
||||
uwsum += 50
|
||||
uwsum += 250
|
||||
|
||||
if uwsum==50300
|
||||
txt.print("1c ok\n")
|
||||
else {
|
||||
txt.print("1c fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
wsum += 100
|
||||
wsum += -50
|
||||
|
||||
if wsum==-29950
|
||||
txt.print("2c ok\n")
|
||||
else {
|
||||
txt.print("2c fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
uwsum = 50000
|
||||
uwsum -= 50
|
||||
uwsum -= 250
|
||||
|
||||
if uwsum==49700
|
||||
txt.print("3c ok\n")
|
||||
else {
|
||||
txt.print("3c fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
wsum = -30000
|
||||
wsum -= 100
|
||||
wsum -= -50
|
||||
|
||||
if wsum==-30050
|
||||
txt.print("4c ok\n")
|
||||
else
|
||||
txt.print("4c fail\n")
|
||||
|
||||
|
||||
uwsum = 50000
|
||||
uwsum += 50 as uword
|
||||
uwsum += -100 as uword
|
||||
|
||||
if uwsum==49950
|
||||
txt.print("5c ok\n")
|
||||
else
|
||||
txt.print("5c fail\n")
|
||||
|
||||
uwsum = 50000
|
||||
uwsum -= 50 as uword
|
||||
uwsum -= 100 as uword
|
||||
|
||||
if uwsum==49850
|
||||
txt.print("6c ok\n")
|
||||
else {
|
||||
txt.print("6c fail:")
|
||||
txt.print_uw(uwsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
wsum += 50
|
||||
wsum += 250
|
||||
|
||||
if wsum==-29700
|
||||
txt.print("7c ok\n")
|
||||
else {
|
||||
txt.print("7c fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
||||
wsum = -30000
|
||||
wsum -= 50
|
||||
wsum -= 250
|
||||
|
||||
if wsum==-30300
|
||||
txt.print("8c ok\n")
|
||||
else {
|
||||
txt.print("8c fail:")
|
||||
txt.print_w(wsum)
|
||||
txt.chrout('\n')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
%import c64lib
|
||||
%import c64textio
|
||||
%target c64
|
||||
%import syslib
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -43,8 +44,10 @@ main {
|
||||
}
|
||||
|
||||
perform_scroll = false
|
||||
txt.scroll_left_full(true)
|
||||
if c64.RASTER & 1
|
||||
txt.scroll_left(true)
|
||||
|
||||
; float the balloon
|
||||
if rnd() & %10000
|
||||
c64.SPXY[1] ++
|
||||
else
|
||||
c64.SPXY[1] --
|
||||
|
@ -1,5 +1,6 @@
|
||||
%import c64lib
|
||||
%import c64textio
|
||||
%target c64
|
||||
%import syslib
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
@ -1,4 +1,6 @@
|
||||
%import c64textio
|
||||
%target c64
|
||||
%import textio
|
||||
%import syslib
|
||||
|
||||
main {
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
%import c64textio
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
%import c64textio
|
||||
%import c64flt
|
||||
%import textio
|
||||
%import floats
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
@ -1,7 +1,9 @@
|
||||
%import c64textio
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
@ -1,6 +1,7 @@
|
||||
%import c64textio
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
%import c64textio
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
|
||||
; Note: this program is compatible with C64 and CX16.
|
||||
|
||||
main {
|
||||
|
||||
|
1008
examples/cmp/comparisons.p8
Normal file
1008
examples/cmp/comparisons.p8
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,93 +0,0 @@
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
byte v1
|
||||
byte v2
|
||||
ubyte cr
|
||||
|
||||
txt.print("signed byte ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
|
||||
; comparisons:
|
||||
v1=-20
|
||||
v2=125
|
||||
txt.print("v1=-20, v2=125\n")
|
||||
compare()
|
||||
|
||||
v1=80
|
||||
v2=80
|
||||
txt.print("v1 = v2 = 80\n")
|
||||
compare()
|
||||
|
||||
v1=20
|
||||
v2=-111
|
||||
txt.print("v1=20, v2=-111\n")
|
||||
compare()
|
||||
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
%import c64textio
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
float v1
|
||||
float v2
|
||||
ubyte cr
|
||||
|
||||
txt.print("floating point ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
|
||||
; comparisons:
|
||||
v1=20
|
||||
v2=666.66
|
||||
txt.print("v1=20, v2=666.66\n")
|
||||
compare()
|
||||
|
||||
v1=-20
|
||||
v2=666.66
|
||||
txt.print("v1=-20, v2=666.66\n")
|
||||
compare()
|
||||
|
||||
v1=666.66
|
||||
v2=555.55
|
||||
txt.print("v1=666.66, v2=555.55\n")
|
||||
compare()
|
||||
|
||||
v1=3.1415
|
||||
v2=-3.1415
|
||||
txt.print("v1 = 3.1415, v2 = -3.1415\n")
|
||||
compare()
|
||||
|
||||
v1=3.1415
|
||||
v2=3.1415
|
||||
txt.print("v1 = v2 = 3.1415\n")
|
||||
compare()
|
||||
|
||||
v1=0
|
||||
v2=0
|
||||
txt.print("v1 = v2 = 0\n")
|
||||
compare()
|
||||
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
ubyte v1
|
||||
ubyte v2
|
||||
ubyte cr
|
||||
|
||||
txt.print("unsigned byte ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
|
||||
; comparisons:
|
||||
v1=20
|
||||
v2=199
|
||||
txt.print("v1=20, v2=199\n")
|
||||
compare()
|
||||
|
||||
v1=80
|
||||
v2=80
|
||||
txt.print("v1 = v2 = 80\n")
|
||||
compare()
|
||||
|
||||
v1=220
|
||||
v2=10
|
||||
txt.print("v1=220, v2=10\n")
|
||||
compare()
|
||||
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,123 +0,0 @@
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
uword v1
|
||||
uword v2
|
||||
ubyte cr
|
||||
|
||||
txt.print("unsigned word ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
|
||||
; comparisons:
|
||||
v1=20
|
||||
v2=$00aa
|
||||
txt.print("v1=20, v2=$00aa\n")
|
||||
compare()
|
||||
|
||||
v1=20
|
||||
v2=$ea00
|
||||
txt.print("v1=20, v2=$ea00\n")
|
||||
compare()
|
||||
|
||||
v1=$c400
|
||||
v2=$22
|
||||
txt.print("v1=$c400, v2=$22\n")
|
||||
compare()
|
||||
|
||||
v1=$c400
|
||||
v2=$2a00
|
||||
txt.print("v1=$c400, v2=$2a00\n")
|
||||
compare()
|
||||
|
||||
v1=$c433
|
||||
v2=$2a00
|
||||
txt.print("v1=$c433, v2=$2a00\n")
|
||||
compare()
|
||||
|
||||
v1=$c433
|
||||
v2=$2aff
|
||||
txt.print("v1=$c433, v2=$2aff\n")
|
||||
compare()
|
||||
|
||||
v1=$aabb
|
||||
v2=$aabb
|
||||
txt.print("v1 = v2 = aabb\n")
|
||||
compare()
|
||||
|
||||
v1=$aa00
|
||||
v2=$aa00
|
||||
txt.print("v1 = v2 = aa00\n")
|
||||
compare()
|
||||
|
||||
v1=$aa
|
||||
v2=$aa
|
||||
txt.print("v1 = v2 = aa\n")
|
||||
compare()
|
||||
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
word v1
|
||||
word v2
|
||||
ubyte cr
|
||||
|
||||
txt.print("signed word ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1!=v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1<v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1>v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1<=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
cr=v1>=v2
|
||||
|
||||
; comparisons:
|
||||
v1=20
|
||||
v2=$00aa
|
||||
txt.print("v1=20, v2=$00aa\n")
|
||||
compare()
|
||||
|
||||
v1=20
|
||||
v2=$7a00
|
||||
txt.print("v1=20, v2=$7a00\n")
|
||||
compare()
|
||||
|
||||
v1=$7400
|
||||
v2=$22
|
||||
txt.print("v1=$7400, v2=$22\n")
|
||||
compare()
|
||||
|
||||
v1=$7400
|
||||
v2=$2a00
|
||||
txt.print("v1=$7400, v2=$2a00\n")
|
||||
compare()
|
||||
|
||||
v1=$7433
|
||||
v2=$2a00
|
||||
txt.print("v1=$7433, v2=$2a00\n")
|
||||
compare()
|
||||
|
||||
v1=$7433
|
||||
v2=$2aff
|
||||
txt.print("v1=$7433, v2=$2aff\n")
|
||||
compare()
|
||||
|
||||
; with negative numbers:
|
||||
v1=-512
|
||||
v2=$00aa
|
||||
txt.print("v1=-512, v2=$00aa\n")
|
||||
compare()
|
||||
|
||||
v1=-512
|
||||
v2=$7a00
|
||||
txt.print("v1=-512, v2=$7a00\n")
|
||||
compare()
|
||||
|
||||
v1=$7400
|
||||
v2=-512
|
||||
txt.print("v1=$7400, v2=-512\n")
|
||||
compare()
|
||||
|
||||
v1=-20000
|
||||
v2=-1000
|
||||
txt.print("v1=-20000, v2=-1000\n")
|
||||
compare()
|
||||
|
||||
v1=-1000
|
||||
v2=-20000
|
||||
txt.print("v1=-1000, v2=-20000\n")
|
||||
compare()
|
||||
|
||||
v1=-1
|
||||
v2=32767
|
||||
txt.print("v1=-1, v2=32767\n")
|
||||
compare()
|
||||
|
||||
v1=32767
|
||||
v2=-1
|
||||
txt.print("v1=32767, v2=-1\n")
|
||||
compare()
|
||||
|
||||
v1=$7abb
|
||||
v2=$7abb
|
||||
txt.print("v1 = v2 = 7abb\n")
|
||||
compare()
|
||||
|
||||
v1=$7a00
|
||||
v2=$7a00
|
||||
txt.print("v1 = v2 = 7a00\n")
|
||||
compare()
|
||||
|
||||
v1=$aa
|
||||
v2=$aa
|
||||
txt.print("v1 = v2 = aa\n")
|
||||
compare()
|
||||
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
txt.print(" Q ")
|
||||
else
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
examples/compiled/diskdir-sys50000.prg
Normal file
BIN
examples/compiled/diskdir-sys50000.prg
Normal file
Binary file not shown.
BIN
examples/compiled/diskdir.prg
Normal file
BIN
examples/compiled/diskdir.prg
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user