mirror of
https://github.com/irmen/prog8.git
synced 2025-06-14 11:23:37 +00:00
Compare commits
202 Commits
Author | SHA1 | Date | |
---|---|---|---|
809917f13b | |||
2b35498370 | |||
f45eabdd9e | |||
438f3ee8d2 | |||
4bea31f051 | |||
5eae7a2b93 | |||
364ef3e55c | |||
e61818f194 | |||
0f9ce319d4 | |||
5d90871789 | |||
88a9e09918 | |||
c50ecf6055 | |||
a18de75da9 | |||
e112dfd910 | |||
9154d8bd37 | |||
0b55372b3b | |||
3ad7fb010f | |||
3f64d1bb5a | |||
a6f564ad88 | |||
d97da3bb7b | |||
a77d3c92ad | |||
6d17e5307c | |||
c2205e473a | |||
4ffb194847 | |||
744cd6ec42 | |||
f08fc18ab5 | |||
462af76770 | |||
9cec554f7c | |||
08b25e610d | |||
e896d5a1a6 | |||
b939562062 | |||
256781bba5 | |||
19705196d6 | |||
3ce692bb10 | |||
78bdbde3ae | |||
8d8c066447 | |||
5da9379c37 | |||
032d20ff37 | |||
d19b17cbfe | |||
4a4f8ff5db | |||
60a9209a14 | |||
0f9e167df3 | |||
2e2b8c498e | |||
144199730f | |||
4bb4eab3b2 | |||
cf9151f669 | |||
aef4598cec | |||
3ada0fdf84 | |||
a5d97b326e | |||
2640015fb1 | |||
6cd42ddafe | |||
1f17c22132 | |||
5c62f612cc | |||
b9ca1c2e2c | |||
93b2ff2e52 | |||
3991d23a69 | |||
1be139759c | |||
d0674ad688 | |||
ffb47458ff | |||
84ec1be8a4 | |||
f4dafec645 | |||
97ce72521d | |||
d2f0e74879 | |||
d9e3895c45 | |||
5075901830 | |||
f1193bb5a0 | |||
d3dc279105 | |||
acc942f690 | |||
e947067dcf | |||
bd9ebf4603 | |||
f41192a52a | |||
ff54d6abd7 | |||
f40bcc219f | |||
679965410a | |||
c6e13ae2a3 | |||
20cdcc673b | |||
89f46222d9 | |||
b27cbfac5e | |||
31c946aeeb | |||
bfc8a26381 | |||
9d98746501 | |||
63b03ba70c | |||
70bab76b36 | |||
15d24d4308 | |||
9ec62eb045 | |||
12f841e30d | |||
335599ed22 | |||
0b717f9e76 | |||
e941f6ecca | |||
ef7744dbda | |||
c83a61c460 | |||
335684caf7 | |||
8d6220ce51 | |||
39ea5c5f99 | |||
b03597ac13 | |||
58f323c087 | |||
513a68584c | |||
88d5c68b32 | |||
14f9382cf9 | |||
cffb582568 | |||
e1812ce16c | |||
7a3163f59a | |||
6f3b2749b0 | |||
c144d4e501 | |||
edfd9d55ba | |||
774897260e | |||
65ba91411d | |||
9cbb8e1a64 | |||
53e9ad5088 | |||
cf6ea63fa6 | |||
1de0ebb7bc | |||
77c1376d6d | |||
353f1954a5 | |||
8bf3406cf8 | |||
936bf9a05c | |||
4487499663 | |||
3976cc26a2 | |||
e6ff87ecd0 | |||
c0887b5f08 | |||
f14dda4eca | |||
bd7f75c130 | |||
fbe3ce008b | |||
7ac6c8f2d1 | |||
fdfbb7bdf0 | |||
1c16bbb742 | |||
9735527062 | |||
402827497e | |||
f81aa0d867 | |||
d32a970101 | |||
cd651aa416 | |||
8a3189123a | |||
b37231d0f5 | |||
3c55719bf1 | |||
af8279a9b9 | |||
c38508c262 | |||
b0e8738ab8 | |||
cae480768e | |||
a70276c190 | |||
0c461ffe2e | |||
237511f2d6 | |||
cdcb652033 | |||
71e678b382 | |||
3050156325 | |||
4bfdbad2e4 | |||
06137ecdc4 | |||
d89f5b0df8 | |||
b6e2b36692 | |||
a6d789cfbc | |||
c07907e7bd | |||
7d8496c874 | |||
164ac56db1 | |||
fdddb8ca64 | |||
a9d4b8b0fa | |||
ec7b9f54c2 | |||
307558a7e7 | |||
febf423eab | |||
a999c23014 | |||
69f1ade595 | |||
b166576e54 | |||
ee2ba5f398 | |||
cb9825484d | |||
76cda82e23 | |||
37b61d9e6b | |||
52f0222a6d | |||
75ccac2f2c | |||
5c771a91f7 | |||
a242ad10e6 | |||
b5086b6a8f | |||
3e47dad12a | |||
235610f40c | |||
6b59559c65 | |||
23e954f716 | |||
983c899cad | |||
c2f9385965 | |||
ceb2c9e4f8 | |||
68a7f9c665 | |||
ffd8d9c7c1 | |||
c66fc8630c | |||
9ca1c66f2b | |||
33647a29d0 | |||
02b12cc762 | |||
3280993e2a | |||
3723c22054 | |||
0a2c4ea0c4 | |||
58a83c0439 | |||
d665489054 | |||
9200992024 | |||
6408cc46a8 | |||
961bcdb7ae | |||
edee70cf31 | |||
1978a9815a | |||
f5e6db9d66 | |||
a94bc40ab0 | |||
534b5ced8f | |||
5ebd9b54e4 | |||
cc4e272526 | |||
295e199bfa | |||
df3371b0f0 | |||
e4fe1d2b8d | |||
b8b9244ffa | |||
3be3989e1c | |||
ed54cf680a |
@ -4,8 +4,8 @@ sudo: false
|
||||
# dist: xenial
|
||||
|
||||
before_install:
|
||||
- chmod +x gradlew
|
||||
- chmod +x ./gradlew
|
||||
|
||||
script:
|
||||
- gradle test
|
||||
- ./gradlew test
|
||||
|
||||
|
73
README.md
73
README.md
@ -2,23 +2,32 @@
|
||||
[](https://travis-ci.org/irmen/prog8)
|
||||
[](https://prog8.readthedocs.io/)
|
||||
|
||||
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
|
||||
===========================================================================
|
||||
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
|
||||
============================================================================
|
||||
|
||||
*Written by Irmen de Jong (irmen@razorvine.net)*
|
||||
|
||||
*Software license: GNU GPL 3.0, see file LICENSE*
|
||||
|
||||
|
||||
This is a structured programming language for the 8-bit 6502/6510 microprocessor from the late 1970's and 1980's
|
||||
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
|
||||
as used in many home computers from that era. It is a medium to low level programming language,
|
||||
which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
|
||||
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
|
||||
|
||||
- reduction of source code length
|
||||
Documentation
|
||||
-------------
|
||||
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
|
||||
https://prog8.readthedocs.io/
|
||||
|
||||
|
||||
What use Prog8 provide?
|
||||
-----------------------
|
||||
|
||||
- reduction of source code length over raw assembly
|
||||
- modularity, symbol scoping, subroutines
|
||||
- various data types other than just bytes (16-bit words, floats, strings)
|
||||
- automatic variable allocations, automatic string and array variables and string sharing
|
||||
- subroutines with a input- and output parameter signature
|
||||
- subroutines with an input- and output parameter signature
|
||||
- constant folding in expressions
|
||||
- conditional branches
|
||||
- 'when' statement to provide a concise jump table alternative to if/elseif chains
|
||||
@ -29,23 +38,24 @@ which aims to provide many conveniences over raw assembly code (even when using
|
||||
- inline assembly allows you to have full control when every cycle or byte matters
|
||||
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||
|
||||
Rapid edit-compile-run-debug cycle:
|
||||
*Rapid edit-compile-run-debug cycle:*
|
||||
|
||||
- use modern PC to work on
|
||||
- quick compilation times (seconds)
|
||||
- option to automatically run the program in the Vice emulator
|
||||
- use a modern PC to do the work on
|
||||
- very quick compilation times
|
||||
- can automatically run the program in the Vice emulator after succesful compilation
|
||||
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
|
||||
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
|
||||
|
||||
It is mainly targeted at the Commodore-64 machine at this time.
|
||||
Contributions to add support for other 8-bit (or other?!) machines are welcome.
|
||||
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
|
||||
|
||||
Documentation/manual
|
||||
--------------------
|
||||
https://prog8.readthedocs.io/
|
||||
- "c64": Commodore-64 (6510 CPU = almost a 6502) premium support.
|
||||
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) experimental support.
|
||||
|
||||
Required tools
|
||||
--------------
|
||||
|
||||
|
||||
|
||||
Additional required tools
|
||||
-------------------------
|
||||
|
||||
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
|
||||
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
|
||||
@ -55,8 +65,9 @@ A **Java runtime (jre or jdk), version 8 or newer** is required to run a prepac
|
||||
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
|
||||
IntelliJ IDEA with the Kotlin plugin).
|
||||
|
||||
It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence
|
||||
of the [Vice emulator](http://vice-emu.sourceforge.net/)
|
||||
It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence
|
||||
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
|
||||
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
|
||||
|
||||
|
||||
Example code
|
||||
@ -64,7 +75,7 @@ Example code
|
||||
|
||||
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -73,35 +84,33 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
ubyte candidate_prime = 2
|
||||
|
||||
sub start() {
|
||||
memset(sieve, 256, false)
|
||||
|
||||
c64scr.print("prime numbers up to 255:\n\n")
|
||||
memset(sieve, 256, false) ; clear the sieve
|
||||
txt.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
while true {
|
||||
repeat {
|
||||
ubyte prime = find_next_prime()
|
||||
if prime==0
|
||||
break
|
||||
c64scr.print_ub(prime)
|
||||
c64scr.print(", ")
|
||||
txt.print_ub(prime)
|
||||
txt.print(", ")
|
||||
amount++
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("number of primes (expected 54): ")
|
||||
c64scr.print_ub(amount)
|
||||
txt.print("number of primes (expected 54): ")
|
||||
txt.print_ub(amount)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
|
||||
sub find_next_prime() -> ubyte {
|
||||
|
||||
while sieve[candidate_prime] {
|
||||
candidate_prime++
|
||||
if candidate_prime==0
|
||||
return 0
|
||||
return 0 ; we wrapped; no more primes available
|
||||
}
|
||||
|
||||
; 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
|
||||
|
@ -1,11 +1,11 @@
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.70"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.3.70"
|
||||
// id "org.jetbrains.kotlin.jvm" version "1.4.0"
|
||||
id 'application'
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
id 'com.github.johnrengelman.shadow' version '5.2.0'
|
||||
@ -110,3 +110,7 @@ dokka {
|
||||
outputFormat = 'html'
|
||||
outputDirectory = "$buildDir/kdoc"
|
||||
}
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
gradleVersion = '6.1.1'
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 9.0 KiB |
@ -1,49 +1,51 @@
|
||||
; --- low level floating point assembly routines for the C64
|
||||
|
||||
|
||||
ub2float .proc
|
||||
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
|
||||
; clobbers A, Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
ldy c64.SCRATCH_ZPB1
|
||||
jsr FREADUY
|
||||
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
|
||||
ldy c64.SCRATCH_ZPWORD2+1
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
lda #0
|
||||
jsr GIVAYF
|
||||
_fac_to_mem ldx P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
jsr MOVMF
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
.pend
|
||||
|
||||
b2float .proc
|
||||
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
|
||||
; clobbers A, Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
lda c64.SCRATCH_ZPB1
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda P8ZP_SCRATCH_B1
|
||||
jsr FREADSA
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
uw2float .proc
|
||||
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr GIVUAYFAY
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
w2float .proc
|
||||
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
ldy c64.SCRATCH_ZPWORD1
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy P8ZP_SCRATCH_W1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
jsr GIVAYF
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
@ -51,8 +53,8 @@ w2float .proc
|
||||
stack_b2float .proc
|
||||
; -- b2float operating on the stack
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda P8ESTACK_LO,x
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr FREADSA
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -60,9 +62,9 @@ stack_b2float .proc
|
||||
stack_w2float .proc
|
||||
; -- w2float operating on the stack
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x
|
||||
lda c64.ESTACK_HI,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
ldy P8ESTACK_LO,x
|
||||
lda P8ESTACK_HI,x
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr GIVAYF
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -70,44 +72,45 @@ stack_w2float .proc
|
||||
stack_ub2float .proc
|
||||
; -- ub2float operating on the stack
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda P8ESTACK_LO,x
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
tay
|
||||
jsr FREADUY
|
||||
lda #0
|
||||
jsr GIVAYF
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
stack_uw2float .proc
|
||||
; -- uw2float operating on the stack
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
ldy c64.ESTACK_HI,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda P8ESTACK_LO,x
|
||||
ldy P8ESTACK_HI,x
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr GIVUAYFAY
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
stack_float2w .proc ; also used for float2b
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr AYINT
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
lda $64
|
||||
sta c64.ESTACK_HI,x
|
||||
sta P8ESTACK_HI,x
|
||||
lda $65
|
||||
sta c64.ESTACK_LO,x
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
stack_float2uw .proc ; also used for float2ub
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr GETADR
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
sta c64.ESTACK_HI,x
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
sta P8ESTACK_HI,x
|
||||
tya
|
||||
sta c64.ESTACK_LO,x
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
@ -115,79 +118,68 @@ stack_float2uw .proc ; also used for float2ub
|
||||
push_float .proc
|
||||
; ---- push mflpt5 in A/Y onto stack
|
||||
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta c64.ESTACK_LO,x
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ESTACK_LO,x
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta c64.ESTACK_HI,x
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta c64.ESTACK_LO,x
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ESTACK_LO,x
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta c64.ESTACK_HI,x
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta c64.ESTACK_LO,x
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_rndf .proc
|
||||
; -- put a random floating point value on the stack
|
||||
stx c64.SCRATCH_ZPREG
|
||||
stx P8ZP_SCRATCH_REG
|
||||
lda #1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx #<_rndf_rnum5
|
||||
ldy #>_rndf_rnum5
|
||||
jsr MOVMF ; fac1 to mem X/Y
|
||||
ldx c64.SCRATCH_ZPREG
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
lda #<_rndf_rnum5
|
||||
ldy #>_rndf_rnum5
|
||||
jmp push_float
|
||||
_rndf_rnum5 .byte 0,0,0,0,0
|
||||
.pend
|
||||
|
||||
push_float_from_indexed_var .proc
|
||||
; -- push the float from the array at A/Y with index on stack, onto the stack.
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
jsr prog8_lib.pop_index_times_5
|
||||
jsr prog8_lib.add_a_to_zpword
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jmp push_float
|
||||
.pend
|
||||
|
||||
pop_float .proc
|
||||
; ---- pops mflpt5 from stack to memory A/Y
|
||||
; (frees 3 stack positions = 6 bytes of which 1 is padding)
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #4
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
lda P8ESTACK_LO,x
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
inx
|
||||
lda c64.ESTACK_HI,x
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
lda P8ESTACK_HI,x
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
lda c64.ESTACK_LO,x
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
lda P8ESTACK_LO,x
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
inx
|
||||
lda c64.ESTACK_HI,x
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
lda P8ESTACK_HI,x
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
lda c64.ESTACK_LO,x
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
lda P8ESTACK_LO,x
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
@ -201,110 +193,74 @@ pop_float_fac1 .proc
|
||||
jmp MOVFM
|
||||
.pend
|
||||
|
||||
pop_float_fac2 .proc
|
||||
; -- pops float from stack into FAC2
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jmp CONUPK
|
||||
.pend
|
||||
|
||||
pop_float_to_indexed_var .proc
|
||||
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
jsr prog8_lib.pop_index_times_5
|
||||
jsr prog8_lib.add_a_to_zpword
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jmp pop_float
|
||||
.pend
|
||||
|
||||
copy_float .proc
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
ldy #0
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
sta _target+1
|
||||
sty _target+2
|
||||
ldy #4
|
||||
_loop lda (P8ZP_SCRATCH_W1),y
|
||||
_target sta $ffff,y ; modified
|
||||
dey
|
||||
bpl _loop
|
||||
rts
|
||||
.pend
|
||||
|
||||
inc_var_f .proc
|
||||
; -- add 1 to float pointed to by A/Y
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr MOVFM
|
||||
lda #<FL_FONE
|
||||
ldy #>FL_FONE
|
||||
jsr FADD
|
||||
ldx c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
ldx P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr MOVMF
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
.pend
|
||||
|
||||
dec_var_f .proc
|
||||
; -- subtract 1 from float pointed to by A/Y
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda #<FL_FONE
|
||||
ldy #>FL_FONE
|
||||
jsr MOVFM
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FSUB
|
||||
ldx c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
ldx P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr MOVMF
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
.pend
|
||||
|
||||
inc_indexed_var_f .proc
|
||||
; -- add 1 to float in array pointed to by A/Y, at index X
|
||||
pha
|
||||
txa
|
||||
sta c64.SCRATCH_ZPB1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1
|
||||
sta c64.SCRATCH_ZPB1
|
||||
pla
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp inc_var_f
|
||||
.pend
|
||||
|
||||
dec_indexed_var_f .proc
|
||||
; -- subtract 1 to float in array pointed to by A/Y, at index X
|
||||
pha
|
||||
txa
|
||||
sta c64.SCRATCH_ZPB1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1
|
||||
sta c64.SCRATCH_ZPB1
|
||||
pla
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp dec_var_f
|
||||
.pend
|
||||
|
||||
|
||||
pop_2_floats_f2_in_fac1 .proc
|
||||
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
||||
@ -330,7 +286,7 @@ push_fac1_as_result .proc
|
||||
jsr MOVMF
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
jmp push_float
|
||||
.pend
|
||||
|
||||
@ -342,21 +298,21 @@ pow_f .proc
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr pop_float
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr CONUPK ; fac2 = float1
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jsr FPWR
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
div_f .proc
|
||||
; -- push f1/f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FDIV
|
||||
@ -366,7 +322,7 @@ div_f .proc
|
||||
add_f .proc
|
||||
; -- push f1+f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FADD
|
||||
@ -376,7 +332,7 @@ add_f .proc
|
||||
sub_f .proc
|
||||
; -- push f1-f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FSUB
|
||||
@ -386,7 +342,7 @@ sub_f .proc
|
||||
mul_f .proc
|
||||
; -- push f1*f2 on stack
|
||||
jsr pop_2_floats_f2_in_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr FMULT
|
||||
@ -396,7 +352,7 @@ mul_f .proc
|
||||
neg_f .proc
|
||||
; -- push -flt back on stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr NEGOP
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -404,7 +360,7 @@ neg_f .proc
|
||||
abs_f .proc
|
||||
; -- push abs(float) on stack (as float)
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr ABS
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -415,24 +371,24 @@ equal_f .proc
|
||||
inx
|
||||
inx
|
||||
inx
|
||||
lda c64.ESTACK_LO-3,x
|
||||
cmp c64.ESTACK_LO,x
|
||||
lda P8ESTACK_LO-3,x
|
||||
cmp P8ESTACK_LO,x
|
||||
bne _equals_false
|
||||
lda c64.ESTACK_LO-2,x
|
||||
cmp c64.ESTACK_LO+1,x
|
||||
lda P8ESTACK_LO-2,x
|
||||
cmp P8ESTACK_LO+1,x
|
||||
bne _equals_false
|
||||
lda c64.ESTACK_LO-1,x
|
||||
cmp c64.ESTACK_LO+2,x
|
||||
lda P8ESTACK_LO-1,x
|
||||
cmp P8ESTACK_LO+2,x
|
||||
bne _equals_false
|
||||
lda c64.ESTACK_HI-2,x
|
||||
cmp c64.ESTACK_HI+1,x
|
||||
lda P8ESTACK_HI-2,x
|
||||
cmp P8ESTACK_HI+1,x
|
||||
bne _equals_false
|
||||
lda c64.ESTACK_HI-1,x
|
||||
cmp c64.ESTACK_HI+2,x
|
||||
lda P8ESTACK_HI-1,x
|
||||
cmp P8ESTACK_HI+2,x
|
||||
bne _equals_false
|
||||
_equals_true lda #1
|
||||
_equals_store inx
|
||||
sta c64.ESTACK_LO+1,x
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
_equals_false lda #0
|
||||
beq _equals_store
|
||||
@ -442,7 +398,7 @@ notequal_f .proc
|
||||
; -- are the two mflpt5 numbers on the stack different?
|
||||
jsr equal_f
|
||||
eor #1 ; invert the result
|
||||
sta c64.ESTACK_LO+1,x
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
@ -495,12 +451,12 @@ compare_floats .proc
|
||||
jsr MOVFM ; fac1 = flt1
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
stx c64.SCRATCH_ZPREG
|
||||
stx P8ZP_SCRATCH_REG
|
||||
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
|
||||
ldx c64.SCRATCH_ZPREG
|
||||
ldx P8ZP_SCRATCH_REG
|
||||
rts
|
||||
_return_false lda #0
|
||||
_return_result sta c64.ESTACK_LO,x
|
||||
_return_result sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
_return_true lda #1
|
||||
@ -510,7 +466,7 @@ _return_true lda #1
|
||||
func_sin .proc
|
||||
; -- push sin(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr SIN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -518,7 +474,7 @@ func_sin .proc
|
||||
func_cos .proc
|
||||
; -- push cos(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr COS
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -526,7 +482,7 @@ func_cos .proc
|
||||
func_tan .proc
|
||||
; -- push tan(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr TAN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -534,7 +490,7 @@ func_tan .proc
|
||||
func_atan .proc
|
||||
; -- push atan(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr ATN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -542,7 +498,7 @@ func_atan .proc
|
||||
func_ln .proc
|
||||
; -- push ln(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr LOG
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -550,7 +506,7 @@ func_ln .proc
|
||||
func_log2 .proc
|
||||
; -- push log base 2, ln(f)/ln(2), back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<c64.FL_LOG2
|
||||
@ -562,7 +518,7 @@ func_log2 .proc
|
||||
|
||||
func_sqrt .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr SQR
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -570,7 +526,7 @@ func_sqrt .proc
|
||||
func_rad .proc
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda #<_pi_div_180
|
||||
ldy #>_pi_div_180
|
||||
jsr FMULT
|
||||
@ -581,7 +537,7 @@ _pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||
func_deg .proc
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda #<_one_over_pi_div_180
|
||||
ldy #>_one_over_pi_div_180
|
||||
jsr FMULT
|
||||
@ -591,7 +547,7 @@ _one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||
|
||||
func_round .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr FADDH
|
||||
jsr INT
|
||||
jmp push_fac1_as_result
|
||||
@ -599,7 +555,7 @@ func_round .proc
|
||||
|
||||
func_floor .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr INT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -607,7 +563,7 @@ func_floor .proc
|
||||
func_ceil .proc
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
@ -625,45 +581,45 @@ func_ceil .proc
|
||||
|
||||
func_any_f .proc
|
||||
inx
|
||||
lda c64.ESTACK_LO,x ; array size
|
||||
sta c64.SCRATCH_ZPB1
|
||||
lda P8ESTACK_LO,x ; array size
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
||||
adc P8ZP_SCRATCH_B1 ; times 5 because of float
|
||||
jmp prog8_lib.func_any_b._entry
|
||||
.pend
|
||||
|
||||
func_all_f .proc
|
||||
inx
|
||||
jsr prog8_lib.peek_address
|
||||
lda c64.ESTACK_LO,x ; array size
|
||||
sta c64.SCRATCH_ZPB1
|
||||
lda P8ESTACK_LO,x ; array size
|
||||
sta P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
||||
adc P8ZP_SCRATCH_B1 ; times 5 because of float
|
||||
tay
|
||||
dey
|
||||
- lda (c64.SCRATCH_ZPWORD1),y
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
clc
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
adc (P8ZP_SCRATCH_W1),y
|
||||
dey
|
||||
cmp #0
|
||||
beq +
|
||||
cpy #255
|
||||
bne -
|
||||
lda #1
|
||||
sta c64.ESTACK_LO+1,x
|
||||
sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
+ sta c64.ESTACK_LO+1,x
|
||||
+ sta P8ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
@ -674,23 +630,23 @@ func_max_f .proc
|
||||
ldy #>_largest_neg_float
|
||||
_minmax_entry jsr MOVFM
|
||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
- sty c64.SCRATCH_ZPREG
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
- sty P8ZP_SCRATCH_REG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FCOMP
|
||||
_minmax_cmp cmp #255 ; modified
|
||||
bne +
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr MOVFM
|
||||
+ lda c64.SCRATCH_ZPWORD1
|
||||
+ lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc #5
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc +
|
||||
inc c64.SCRATCH_ZPWORD1+1
|
||||
+ ldy c64.SCRATCH_ZPREG
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
+ ldy P8ZP_SCRATCH_REG
|
||||
dey
|
||||
cpy #255
|
||||
bne -
|
||||
@ -709,25 +665,25 @@ _largest_pos_float .byte 255,127,255,255,255 ; largest positive float
|
||||
.pend
|
||||
|
||||
func_sum_f .proc
|
||||
lda #<FL_ZERO
|
||||
ldy #>FL_ZERO
|
||||
lda #<ZERO
|
||||
ldy #>ZERO
|
||||
jsr MOVFM
|
||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
- sty c64.SCRATCH_ZPREG
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
- sty P8ZP_SCRATCH_REG
|
||||
lda P8ZP_SCRATCH_W1
|
||||
ldy P8ZP_SCRATCH_W1+1
|
||||
jsr FADD
|
||||
ldy c64.SCRATCH_ZPREG
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
dey
|
||||
cpy #255
|
||||
beq +
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
clc
|
||||
adc #5
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
bcc -
|
||||
inc c64.SCRATCH_ZPWORD1+1
|
||||
inc P8ZP_SCRATCH_W1+1
|
||||
bne -
|
||||
+ jmp push_fac1_as_result
|
||||
.pend
|
||||
@ -735,7 +691,7 @@ func_sum_f .proc
|
||||
sign_f .proc
|
||||
jsr pop_float_fac1
|
||||
jsr SIGN
|
||||
sta c64.ESTACK_LO,x
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
@ -744,22 +700,22 @@ sign_f .proc
|
||||
set_0_array_float .proc
|
||||
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
lda P8ESTACK_LO,x
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.ESTACK_LO,x
|
||||
adc P8ESTACK_LO,x
|
||||
tay
|
||||
lda #0
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
iny
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
@ -767,17 +723,31 @@ set_0_array_float .proc
|
||||
set_array_float .proc
|
||||
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
lda P8ESTACK_LO,x
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.ESTACK_LO,x
|
||||
clc
|
||||
adc c64.SCRATCH_ZPWORD2
|
||||
ldy c64.SCRATCH_ZPWORD2+1
|
||||
adc P8ESTACK_LO,x
|
||||
adc P8ZP_SCRATCH_W2
|
||||
ldy P8ZP_SCRATCH_W2+1
|
||||
bcc +
|
||||
iny
|
||||
+ jmp copy_float
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
.pend
|
||||
|
||||
|
||||
swap_floats .proc
|
||||
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
|
||||
ldy #4
|
||||
- lda (P8ZP_SCRATCH_W1),y
|
||||
pha
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
sta (P8ZP_SCRATCH_W1),y
|
||||
pla
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
dey
|
||||
bpl -
|
||||
rts
|
||||
.pend
|
||||
|
@ -6,12 +6,12 @@
|
||||
|
||||
%option enable_floats
|
||||
|
||||
|
||||
c64flt {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
const float ZERO = 0.0
|
||||
|
||||
|
||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||
@ -35,13 +35,11 @@ c64flt {
|
||||
&float FL_TWOPI = $e2e5 ; 2 * PI
|
||||
&float FL_FR4 = $e2ea ; .25
|
||||
; oddly enough, 0.0 isn't available in the kernel.
|
||||
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
|
||||
|
||||
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||
|
||||
; checked functions below:
|
||||
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
|
||||
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
@ -91,6 +89,7 @@ romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1
|
||||
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
|
||||
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
|
||||
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
|
||||
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||
@ -163,9 +162,9 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||
%asm {{
|
||||
sta c64.SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
ldy c64.SCRATCH_ZPREG
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||
}}
|
||||
}
|
||||
@ -174,9 +173,9 @@ asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to signed word in A/Y
|
||||
%asm {{
|
||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||
sta c64.SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
ldy c64.SCRATCH_ZPREG
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -185,41 +184,34 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to unsigned word in A/Y
|
||||
%asm {{
|
||||
jsr GETADR ; this uses the inverse order, Y/A
|
||||
sta c64.SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy c64.SCRATCH_ZPB1
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_f (float value) {
|
||||
; ---- prints the floating point value (without a newline) using basic rom routines.
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
jsr c64.STROUT ; print string in A/Y
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_fln (float value) {
|
||||
; ---- prints the floating point value (with a newline at the end) using basic rom routines
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FPRINTLN ; print fac1 with newline
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
}}
|
||||
|
||||
}
|
||||
|
||||
%asminclude "library:c64floats.asm", ""
|
||||
|
||||
} ; ------ end of block c64flt
|
||||
}
|
||||
|
243
compiler/res/prog8lib/c64graphics.p8
Normal file
243
compiler/res/prog8lib/c64graphics.p8
Normal file
@ -0,0 +1,243 @@
|
||||
%import c64textio
|
||||
|
||||
; bitmap pixel graphics module for the C64
|
||||
; only black/white monchrome for now
|
||||
; assumes bitmap screen memory is $2000-$3fff
|
||||
|
||||
graphics {
|
||||
const uword bitmap_address = $2000
|
||||
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
c64.SCROLY |= %00100000
|
||||
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
|
||||
clear_screen(1, 0)
|
||||
}
|
||||
|
||||
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
|
||||
memset(bitmap_address, 320*200/8, 0)
|
||||
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
|
||||
}
|
||||
|
||||
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
|
||||
; Bresenham algorithm.
|
||||
; This code special cases various quadrant loops to allow simple ++ and -- operations.
|
||||
; TODO rewrite this in optimized assembly
|
||||
if y1>y2 {
|
||||
; make sure dy is always positive to avoid 8 instead of just 4 special cases
|
||||
swap(x1, x2)
|
||||
swap(y1, y2)
|
||||
}
|
||||
word @zp d = 0
|
||||
ubyte positive_ix = true
|
||||
word @zp dx = x2 - x1 as word
|
||||
word @zp dy = y2 as word - y1 as word
|
||||
if dx < 0 {
|
||||
dx = -dx
|
||||
positive_ix = false
|
||||
}
|
||||
dx *= 2
|
||||
dy *= 2
|
||||
internal_plotx = x1
|
||||
|
||||
if dx >= dy {
|
||||
if positive_ix {
|
||||
repeat {
|
||||
internal_plot(y1)
|
||||
if internal_plotx==x2
|
||||
return
|
||||
internal_plotx++
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
internal_plot(y1)
|
||||
if internal_plotx==x2
|
||||
return
|
||||
internal_plotx--
|
||||
d += dy
|
||||
if d > dx {
|
||||
y1++
|
||||
d -= dx
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if positive_ix {
|
||||
repeat {
|
||||
internal_plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
if d > dy {
|
||||
internal_plotx++
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
} else {
|
||||
repeat {
|
||||
internal_plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx
|
||||
if d > dy {
|
||||
internal_plotx--
|
||||
d -= dy
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Midpoint algorithm
|
||||
ubyte @zp ploty
|
||||
ubyte @zp xx = radius
|
||||
ubyte @zp yy = 0
|
||||
byte @zp decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
internal_plotx = xcenter + xx
|
||||
ploty = ycenter + yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - xx
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + xx
|
||||
ploty = ycenter - yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - xx
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + yy
|
||||
ploty = ycenter + xx
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - yy
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter + yy
|
||||
ploty = ycenter - xx
|
||||
internal_plot(ploty)
|
||||
internal_plotx = xcenter - yy
|
||||
internal_plot(ploty)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword cx, ubyte cy, ubyte radius) {
|
||||
; Midpoint algorithm, filled
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx as byte
|
||||
|
||||
while xx>=yy {
|
||||
ubyte cy_plus_yy = cy + yy
|
||||
ubyte cy_min_yy = cy - yy
|
||||
ubyte cy_plus_xx = cy + xx
|
||||
ubyte cy_min_xx = cy - xx
|
||||
|
||||
for internal_plotx in cx to cx+xx {
|
||||
internal_plot(cy_plus_yy)
|
||||
internal_plot(cy_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 cx to cx+yy {
|
||||
internal_plot(cy_plus_xx)
|
||||
internal_plot(cy_min_xx)
|
||||
}
|
||||
for internal_plotx in cx-yy to cx {
|
||||
internal_plot(cy_plus_xx)
|
||||
internal_plot(cy_min_xx)
|
||||
}
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; here is the non-asm code for the plot routine below:
|
||||
; sub plot_nonasm(uword px, ubyte py) {
|
||||
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||
; uword addr = bitmap_address + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
|
||||
; @(addr) |= ormask[lsb(px) & 7]
|
||||
; }
|
||||
|
||||
; TODO fix the use of X (or XY) as parameter so we can actually use this plot() routine
|
||||
; calling it with a byte results in a compiler crash, calling it with word results in clobbering X register I think
|
||||
asmsub plotXXX(uword plotx @XY, ubyte ploty @A) {
|
||||
%asm {{
|
||||
stx internal_plotx
|
||||
sty internal_plotx+1
|
||||
jmp internal_plot
|
||||
}}
|
||||
}
|
||||
|
||||
uword internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
|
||||
|
||||
asmsub internal_plot(ubyte ploty @A) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||
%asm {{
|
||||
tay
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda internal_plotx+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lsr a ; 0
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda internal_plotx
|
||||
pha
|
||||
and #7
|
||||
tax
|
||||
|
||||
lda _y_lookup_lo,y
|
||||
clc
|
||||
adc P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda _y_lookup_hi,y
|
||||
adc P8ZP_SCRATCH_W2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
|
||||
pla ; internal_plotx
|
||||
and #%11111000
|
||||
tay
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
ora _ormask,x
|
||||
sta (P8ZP_SCRATCH_W2),y
|
||||
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
|
||||
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
|
||||
|
||||
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
|
||||
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
|
||||
; the y lookup tables encodes this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||
; 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)
|
||||
|
||||
_y_lookup_lo .byte <_plot_y_values
|
||||
_y_lookup_hi .byte >_plot_y_values
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -7,15 +7,6 @@
|
||||
|
||||
|
||||
c64 {
|
||||
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
|
||||
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
|
||||
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
|
||||
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
|
||||
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
|
||||
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||
|
||||
|
||||
&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
|
||||
@ -183,19 +174,11 @@ c64 {
|
||||
; ---- end of SID registers ----
|
||||
|
||||
|
||||
|
||||
; ---- C64 basic routines ----
|
||||
|
||||
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
||||
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||
|
||||
|
||||
; ---- end of C64 basic routines ----
|
||||
|
||||
|
||||
; ---- C64 kernal routines ----
|
||||
; ---- C64 ROM kernal routines ----
|
||||
|
||||
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
|
||||
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
|
||||
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
|
||||
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
|
||||
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
@ -238,6 +221,212 @@ romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
; ---- end of C64 kernal routines ----
|
||||
; ---- end of C64 ROM kernal routines ----
|
||||
|
||||
|
||||
; ---- C64 specific system utility routines: ----
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
|
||||
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
|
||||
; Also a different color scheme is chosen to identify ourselves a little.
|
||||
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
lda #%00101111
|
||||
sta $00
|
||||
lda #%00100111
|
||||
sta $01
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
lda #6
|
||||
sta c64.EXTCOL
|
||||
lda #7
|
||||
sta c64.COLOR
|
||||
lda #0
|
||||
sta c64.BGCOL0
|
||||
tax
|
||||
tay
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irqvec_excl() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta c64.CINV
|
||||
lda #>_irq_handler
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
|
||||
jmp c64.IRQDFEND ; end irq processing - don't call kernel
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_irqvec() clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<_irq_handler
|
||||
sta c64.CINV
|
||||
lda #>_irq_handler
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
rts
|
||||
_irq_handler jsr _irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr _irq_handler_end
|
||||
jmp c64.IRQDFRT ; continue with normal kernel irq routine
|
||||
|
||||
_irq_handler_init
|
||||
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
|
||||
stx IRQ_X_REG
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta IRQ_SCRATCH_ZPB1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
sta IRQ_SCRATCH_ZPREG
|
||||
lda P8ZP_SCRATCH_REG_X
|
||||
sta IRQ_SCRATCH_ZPREGX
|
||||
lda P8ZP_SCRATCH_W1
|
||||
sta IRQ_SCRATCH_ZPWORD1
|
||||
lda P8ZP_SCRATCH_W1+1
|
||||
sta IRQ_SCRATCH_ZPWORD1+1
|
||||
lda P8ZP_SCRATCH_W2
|
||||
sta IRQ_SCRATCH_ZPWORD2
|
||||
lda P8ZP_SCRATCH_W2+1
|
||||
sta IRQ_SCRATCH_ZPWORD2+1
|
||||
; stack protector; make sure we don't clobber the top of the evaluation stack
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
dex
|
||||
cld
|
||||
rts
|
||||
|
||||
_irq_handler_end
|
||||
; restore all zp scratch registers and the X register
|
||||
lda IRQ_SCRATCH_ZPB1
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda IRQ_SCRATCH_ZPREG
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda IRQ_SCRATCH_ZPREGX
|
||||
sta P8ZP_SCRATCH_REG_X
|
||||
lda IRQ_SCRATCH_ZPWORD1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda IRQ_SCRATCH_ZPWORD1+1
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda IRQ_SCRATCH_ZPWORD2
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda IRQ_SCRATCH_ZPWORD2+1
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
ldx IRQ_X_REG
|
||||
rts
|
||||
|
||||
IRQ_X_REG .byte 0
|
||||
IRQ_SCRATCH_ZPB1 .byte 0
|
||||
IRQ_SCRATCH_ZPREG .byte 0
|
||||
IRQ_SCRATCH_ZPREGX .byte 0
|
||||
IRQ_SCRATCH_ZPWORD1 .word 0
|
||||
IRQ_SCRATCH_ZPWORD2 .word 0
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub restore_irqvec() {
|
||||
%asm {{
|
||||
sei
|
||||
lda #<c64.IRQDFRT
|
||||
sta c64.CINV
|
||||
lda #>c64.IRQDFRT
|
||||
sta c64.CINV+1
|
||||
lda #0
|
||||
sta c64.IREQMASK ; disable raster irq
|
||||
lda #%10000001
|
||||
sta c64.CIA1ICR ; restore CIA1 irq
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
jsr _setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
sta c64.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
jmp c64.IRQDFRT
|
||||
|
||||
_setup_raster_irq
|
||||
pha
|
||||
lda #%01111111
|
||||
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
|
||||
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
|
||||
and c64.SCROLY
|
||||
sta c64.SCROLY ; clear most significant bit of raster position
|
||||
lda c64.CIA1ICR ; ack previous irq
|
||||
lda c64.CIA2ICR ; ack previous irq
|
||||
pla
|
||||
sta c64.RASTER ; set the raster line number where interrupt should occur
|
||||
cpy #0
|
||||
beq +
|
||||
lda c64.SCROLY
|
||||
ora #%10000000
|
||||
sta c64.SCROLY ; set most significant bit of raster position
|
||||
+ lda #%00000001
|
||||
sta c64.IREQMASK ;enable raster interrupt signals from vic
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) {
|
||||
%asm {{
|
||||
sei
|
||||
jsr set_rasterirq._setup_raster_irq
|
||||
lda #<_raster_irq_handler
|
||||
sta c64.CINV
|
||||
lda #>_raster_irq_handler
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
jmp c64.IRQDFEND ; end irq processing - don't call kernel
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
; ---- end of C64 specific system utility routines ----
|
||||
|
||||
|
||||
}
|
||||
|
557
compiler/res/prog8lib/c64textio.p8
Normal file
557
compiler/res/prog8lib/c64textio.p8
Normal file
@ -0,0 +1,557 @@
|
||||
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
%import c64lib
|
||||
%import conv
|
||||
|
||||
|
||||
txt {
|
||||
|
||||
asmsub clear_screen() {
|
||||
%asm {{
|
||||
lda #' '
|
||||
jmp clear_screenchars
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub fill_screen (ubyte char @ A, ubyte charcolor @ 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)
|
||||
|
||||
%asm {{
|
||||
pha
|
||||
tya
|
||||
jsr clear_screencolors
|
||||
pla
|
||||
jsr clear_screenchars
|
||||
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 {{
|
||||
ldy #0
|
||||
_loop sta c64.Screen,y
|
||||
sta c64.Screen+$0100,y
|
||||
sta c64.Screen+$0200,y
|
||||
sta c64.Screen+$02e8,y
|
||||
iny
|
||||
bne _loop
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub clear_screencolors (ubyte scrcolor @ A) clobbers(Y) {
|
||||
; ---- clear the character screen colors with the given color (leaves characters).
|
||||
; (assumes color matrix is at the default address)
|
||||
%asm {{
|
||||
ldy #0
|
||||
_loop sta c64.Colors,y
|
||||
sta c64.Colors+$0100,y
|
||||
sta c64.Colors+$0200,y
|
||||
sta c64.Colors+$02e8,y
|
||||
iny
|
||||
bne _loop
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub color (ubyte txtcol) {
|
||||
c64.COLOR = txtcol
|
||||
}
|
||||
|
||||
asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
|
||||
; ---- scroll the whole screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
; Carry flag determines if screen color data must be scrolled too
|
||||
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Colors + 40*row + 1,x
|
||||
sta c64.Colors + 40*row,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
bpl -
|
||||
|
||||
_scroll_screen ; scroll the screen memory
|
||||
ldx #0
|
||||
ldy #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 1,x
|
||||
sta c64.Screen + 40*row,x
|
||||
.next
|
||||
inx
|
||||
dey
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_right_full (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
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Colors + 40*row + 0,x
|
||||
sta c64.Colors + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
_scroll_screen ; scroll the screen memory
|
||||
ldx #38
|
||||
-
|
||||
.for row=0, row<=24, row+=1
|
||||
lda c64.Screen + 40*row + 0,x
|
||||
sta c64.Screen + 40*row + 1,x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_up_full (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
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
_scroll_screen ; scroll the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=1, row<=24, row+=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row-1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub scroll_down_full (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
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
bcs +
|
||||
jmp _scroll_screen
|
||||
|
||||
+ ; scroll the color memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Colors + 40*row,x
|
||||
sta c64.Colors + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
_scroll_screen ; scroll the screen memory
|
||||
ldx #39
|
||||
-
|
||||
.for row=23, row>=0, row-=1
|
||||
lda c64.Screen + 40*row,x
|
||||
sta c64.Screen + 40*(row+1),x
|
||||
.next
|
||||
dex
|
||||
bpl -
|
||||
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
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 {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr conv.ubyte2decimal
|
||||
pha
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
jsr c64.CHROUT
|
||||
txa
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
|
||||
; ---- print the ubyte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
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
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_b (byte value @ A) clobbers(A,Y) {
|
||||
; ---- print the byte in A in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
pha
|
||||
cmp #0
|
||||
bpl +
|
||||
lda #'-'
|
||||
jsr c64.CHROUT
|
||||
+ pla
|
||||
jsr conv.byte2decimal
|
||||
jsr print_ub._print_byte_digits
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
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 {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
bcc +
|
||||
pha
|
||||
lda #'$'
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
+ jsr conv.ubyte2hex
|
||||
jsr c64.CHROUT
|
||||
tya
|
||||
jsr c64.CHROUT
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
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 {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
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 -
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
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 {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr conv.uword2decimal
|
||||
ldy #0
|
||||
- lda conv.uword2decimal.decTenThousands,y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
+ ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- print the uword in A/Y in decimal form, without left padding 0s
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
jsr conv.uword2decimal
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
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 @Y, ubyte row @A) clobbers(A) {
|
||||
; ---- set the character in SCRATCH_ZPB1 on the screen matrix at the given position
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
tay
|
||||
lda _screenrows+1,y
|
||||
sta _mod+2
|
||||
lda _screenrows,y
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
sta _mod+1
|
||||
bcc +
|
||||
inc _mod+2
|
||||
+ lda P8ZP_SCRATCH_B1
|
||||
_mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_screenrows .word $0400 + range(0, 1000, 40)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub getchr (ubyte col @Y, ubyte row @A) clobbers(Y) -> ubyte @ A {
|
||||
; ---- get the character in the screen matrix at the given location
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows+1,y
|
||||
sta _mod+2
|
||||
lda setchr._screenrows,y
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
sta _mod+1
|
||||
bcc _mod
|
||||
inc _mod+2
|
||||
_mod lda $ffff ; modified
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub setclr (ubyte col @Y, ubyte row @A) clobbers(A) {
|
||||
; ---- set the color in SCRATCH_ZPB1 on the screen matrix at the given position
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
tay
|
||||
lda _colorrows+1,y
|
||||
sta _mod+2
|
||||
lda _colorrows,y
|
||||
clc
|
||||
adc P8ZP_SCRATCH_REG
|
||||
sta _mod+1
|
||||
bcc +
|
||||
inc _mod+2
|
||||
+ lda P8ZP_SCRATCH_B1
|
||||
_mod sta $ffff ; modified
|
||||
rts
|
||||
|
||||
_colorrows .word $d800 + range(0, 1000, 40)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub getclr (ubyte col @Y, ubyte row @A) clobbers(Y) -> ubyte @ A {
|
||||
; ---- get the color in the screen color matrix at the given location
|
||||
%asm {{
|
||||
sty P8ZP_SCRATCH_B1
|
||||
asl a
|
||||
tay
|
||||
lda setclr._colorrows+1,y
|
||||
sta _mod+2
|
||||
lda setclr._colorrows,y
|
||||
clc
|
||||
adc P8ZP_SCRATCH_B1
|
||||
sta _mod+1
|
||||
bcc _mod
|
||||
inc _mod+2
|
||||
_mod lda $ffff ; modified
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
|
||||
; ---- set char+color at the given position on the screen
|
||||
%asm {{
|
||||
lda row
|
||||
asl a
|
||||
tay
|
||||
lda setchr._screenrows+1,y
|
||||
sta _charmod+2
|
||||
adc #$d4
|
||||
sta _colormod+2
|
||||
lda setchr._screenrows,y
|
||||
clc
|
||||
adc column
|
||||
sta _charmod+1
|
||||
sta _colormod+1
|
||||
bcc +
|
||||
inc _charmod+2
|
||||
inc _colormod+2
|
||||
+ lda char
|
||||
_charmod sta $ffff ; modified
|
||||
lda charcolor
|
||||
_colormod sta $ffff ; modified
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
|
||||
; ---- safe wrapper around PLOT kernel routine, to save the X register.
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
tax
|
||||
clc
|
||||
jsr c64.PLOT
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
361
compiler/res/prog8lib/conv.p8
Normal file
361
compiler/res/prog8lib/conv.p8
Normal file
@ -0,0 +1,361 @@
|
||||
; Prog8 definitions for number conversions routines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
conv {
|
||||
|
||||
; ----- number conversions to decimal strings
|
||||
|
||||
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
%asm {{
|
||||
ldy #uword2decimal.ASCII_0_OFFSET
|
||||
bne uword2decimal.hex_try200
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub uword2decimal (uword value @ AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
; ---- convert 16 bit uword in A/Y to decimal
|
||||
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
|
||||
; (these are terminated by a zero byte so they can be easily printed)
|
||||
; also returns Y = 100's, A = 10's, X = 1's
|
||||
|
||||
%asm {{
|
||||
|
||||
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||
;By Omegamatrix Further optimizations by tepples
|
||||
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
|
||||
|
||||
;HexToDec99
|
||||
; start in A
|
||||
; end with A = 10's, decOnes (also in X)
|
||||
|
||||
;HexToDec255
|
||||
; start in A
|
||||
; end with Y = 100's, A = 10's, decOnes (also in X)
|
||||
|
||||
;HexToDec999
|
||||
; start with A = high byte, Y = low byte
|
||||
; end with Y = 100's, A = 10's, decOnes (also in X)
|
||||
; requires 1 extra temp register on top of decOnes, could combine
|
||||
; these two if HexToDec65535 was eliminated...
|
||||
|
||||
;HexToDec65535
|
||||
; start with A/Y (low/high) as 16 bit value
|
||||
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
|
||||
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
|
||||
|
||||
|
||||
ASCII_0_OFFSET = $30
|
||||
temp = P8ZP_SCRATCH_B1 ; byte in zeropage
|
||||
hexHigh = P8ZP_SCRATCH_W1 ; byte in zeropage
|
||||
hexLow = P8ZP_SCRATCH_W1+1 ; byte in zeropage
|
||||
|
||||
|
||||
HexToDec65535; SUBROUTINE
|
||||
sty hexHigh ;3 @9
|
||||
sta hexLow ;3 @12
|
||||
tya
|
||||
tax ;2 @14
|
||||
lsr a ;2 @16
|
||||
lsr a ;2 @18 integer divide 1024 (result 0-63)
|
||||
|
||||
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
|
||||
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
|
||||
|
||||
;at this point we have a number 1-65 that we have to times by 24,
|
||||
;add to original sum, and Mod 1024 to get a remainder 0-999
|
||||
|
||||
|
||||
sta temp ;3 @25
|
||||
asl a ;2 @27
|
||||
adc temp ;3 @30 x3
|
||||
tay ;2 @32
|
||||
lsr a ;2 @34
|
||||
lsr a ;2 @36
|
||||
lsr a ;2 @38
|
||||
lsr a ;2 @40
|
||||
lsr a ;2 @42
|
||||
tax ;2 @44
|
||||
tya ;2 @46
|
||||
asl a ;2 @48
|
||||
asl a ;2 @50
|
||||
asl a ;2 @52
|
||||
clc ;2 @54
|
||||
adc hexLow ;3 @57
|
||||
sta hexLow ;3 @60
|
||||
txa ;2 @62
|
||||
adc hexHigh ;3 @65
|
||||
sta hexHigh ;3 @68
|
||||
ror a ;2 @70
|
||||
lsr a ;2 @72
|
||||
tay ;2 @74 integer divide 1,000 (result 0-65)
|
||||
|
||||
lsr a ;2 @76 split the 1,000 and 10,000 digit
|
||||
tax ;2 @78
|
||||
lda ShiftedBcdTab,x ;4 @82
|
||||
tax ;2 @84
|
||||
rol a ;2 @86
|
||||
and #$0F ;2 @88
|
||||
ora #ASCII_0_OFFSET
|
||||
sta decThousands ;3 @91
|
||||
txa ;2 @93
|
||||
lsr a ;2 @95
|
||||
lsr a ;2 @97
|
||||
lsr a ;2 @99
|
||||
ora #ASCII_0_OFFSET
|
||||
sta decTenThousands ;3 @102
|
||||
|
||||
lda hexLow ;3 @105
|
||||
cpy temp ;3 @108
|
||||
bmi _doSubtract ;2³ @110/111
|
||||
beq _useZero ;2³ @112/113
|
||||
adc #23 + 24 ;2 @114
|
||||
_doSubtract
|
||||
sbc #23 ;2 @116
|
||||
sta hexLow ;3 @119
|
||||
_useZero
|
||||
lda hexHigh ;3 @122
|
||||
sbc #0 ;2 @124
|
||||
|
||||
Start100s
|
||||
and #$03 ;2 @126
|
||||
tax ;2 @128 0,1,2,3
|
||||
cmp #2 ;2 @130
|
||||
rol a ;2 @132 0,2,5,7
|
||||
ora #ASCII_0_OFFSET
|
||||
tay ;2 @134 Y = Hundreds digit
|
||||
|
||||
lda hexLow ;3 @137
|
||||
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
|
||||
bcs hex_doSub200 ;2³ @143/144
|
||||
|
||||
hex_try200
|
||||
cmp #200 ;2 @145
|
||||
bcc hex_try100 ;2³ @147/148
|
||||
hex_doSub200
|
||||
iny ;2 @149
|
||||
iny ;2 @151
|
||||
sbc #200 ;2 @153
|
||||
hex_try100
|
||||
cmp #100 ;2 @155
|
||||
bcc HexToDec99 ;2³ @157/158
|
||||
iny ;2 @159
|
||||
sbc #100 ;2 @161
|
||||
|
||||
HexToDec99; SUBROUTINE
|
||||
lsr a ;2 @163
|
||||
tax ;2 @165
|
||||
lda ShiftedBcdTab,x ;4 @169
|
||||
tax ;2 @171
|
||||
rol a ;2 @173
|
||||
and #$0F ;2 @175
|
||||
ora #ASCII_0_OFFSET
|
||||
sta decOnes ;3 @178
|
||||
txa ;2 @180
|
||||
lsr a ;2 @182
|
||||
lsr a ;2 @184
|
||||
lsr a ;2 @186
|
||||
ora #ASCII_0_OFFSET
|
||||
|
||||
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
|
||||
sty decHundreds
|
||||
sta decTens
|
||||
ldx decOnes
|
||||
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
|
||||
|
||||
|
||||
HexToDec999; SUBROUTINE
|
||||
sty hexLow ;3 @9
|
||||
jmp Start100s ;3 @12
|
||||
|
||||
Mod100Tab
|
||||
.byte 0,56,12,56+12
|
||||
|
||||
ShiftedBcdTab
|
||||
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
|
||||
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
|
||||
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
|
||||
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
|
||||
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
|
||||
|
||||
decTenThousands .byte 0
|
||||
decThousands .byte 0
|
||||
decHundreds .byte 0
|
||||
decTens .byte 0
|
||||
decOnes .byte 0
|
||||
.byte 0 ; zero-terminate the decimal output string
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
; ----- utility functions ----
|
||||
|
||||
|
||||
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
|
||||
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
|
||||
; note: if the number is negative, you have to deal with the '-' yourself!
|
||||
%asm {{
|
||||
cmp #0
|
||||
bpl +
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
+ jmp ubyte2decimal
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
|
||||
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
pha
|
||||
and #$0f
|
||||
tax
|
||||
ldy _hex_digits,x
|
||||
pla
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
tax
|
||||
lda _hex_digits,x
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
rts
|
||||
|
||||
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
|
||||
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
jsr ubyte2hex
|
||||
sta output
|
||||
sty output+1
|
||||
lda P8ZP_SCRATCH_REG
|
||||
jsr ubyte2hex
|
||||
sta output+2
|
||||
sty output+3
|
||||
rts
|
||||
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2uword(str string @ AY) -> uword @ AY {
|
||||
; -- returns the unsigned word value of the string number argument in AY
|
||||
; the number may NOT be preceded by a + sign and may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
_result = P8ZP_SCRATCH_W2
|
||||
sta _mod+1
|
||||
sty _mod+2
|
||||
ldy #0
|
||||
sty _result
|
||||
sty _result+1
|
||||
_mod lda $ffff,y ; modified
|
||||
sec
|
||||
sbc #48
|
||||
bpl +
|
||||
_done ; return result
|
||||
lda _result
|
||||
ldy _result+1
|
||||
rts
|
||||
+ cmp #10
|
||||
bcs _done
|
||||
; add digit to result
|
||||
pha
|
||||
jsr _result_times_10
|
||||
pla
|
||||
clc
|
||||
adc _result
|
||||
sta _result
|
||||
bcc +
|
||||
inc _result+1
|
||||
+ iny
|
||||
bne _mod
|
||||
; never reached
|
||||
|
||||
_result_times_10 ; (W*4 + W)*2
|
||||
lda _result+1
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda _result
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
asl a
|
||||
rol P8ZP_SCRATCH_REG
|
||||
clc
|
||||
adc _result
|
||||
sta _result
|
||||
lda P8ZP_SCRATCH_REG
|
||||
adc _result+1
|
||||
asl _result
|
||||
rol a
|
||||
sta _result+1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2word(str string @ AY) -> word @ AY {
|
||||
; -- returns the signed word value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
_result = P8ZP_SCRATCH_W2
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
sty _result
|
||||
sty _result+1
|
||||
sty _negative
|
||||
lda (P8ZP_SCRATCH_W1),y
|
||||
cmp #'+'
|
||||
bne +
|
||||
iny
|
||||
+ cmp #'-'
|
||||
bne _parse
|
||||
inc _negative
|
||||
iny
|
||||
_parse lda (P8ZP_SCRATCH_W1),y
|
||||
sec
|
||||
sbc #48
|
||||
bpl _digit
|
||||
_done ; return result
|
||||
lda _negative
|
||||
beq +
|
||||
sec
|
||||
lda #0
|
||||
sbc _result
|
||||
sta _result
|
||||
lda #0
|
||||
sbc _result+1
|
||||
sta _result+1
|
||||
+ lda _result
|
||||
ldy _result+1
|
||||
rts
|
||||
_digit cmp #10
|
||||
bcs _done
|
||||
; add digit to result
|
||||
pha
|
||||
jsr str2uword._result_times_10
|
||||
pla
|
||||
clc
|
||||
adc _result
|
||||
sta _result
|
||||
bcc +
|
||||
inc _result+1
|
||||
+ iny
|
||||
bne _parse
|
||||
; never reached
|
||||
_negative .byte 0
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
153
compiler/res/prog8lib/cx16flt.p8
Normal file
153
compiler/res/prog8lib/cx16flt.p8
Normal file
@ -0,0 +1,153 @@
|
||||
; Prog8 definitions for floating point handling on the CommanderX16
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%option enable_floats
|
||||
|
||||
c64flt {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
const float ZERO = 0.0
|
||||
|
||||
|
||||
; ---- ROM float functions ----
|
||||
|
||||
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||
|
||||
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
|
||||
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
|
||||
|
||||
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
|
||||
; (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)
|
||||
|
||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $fe06 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
|
||||
romsub $fe09 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
|
||||
romsub $fe0c = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
|
||||
romsub $fe0f = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
|
||||
romsub $fe12 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
|
||||
romsub $fe15 = FADDT() clobbers(A,X,Y) ; fac1 += fac2
|
||||
romsub $fe1b = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
|
||||
romsub $fe1e = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
|
||||
romsub $fe24 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
|
||||
romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
|
||||
romsub $fe2a = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
|
||||
romsub $fe33 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
|
||||
romsub $fe36 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
|
||||
romsub $fe3c = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
|
||||
romsub $fe3f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
|
||||
romsub $fe42 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
|
||||
|
||||
romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
|
||||
romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
|
||||
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
|
||||
romsub $fe54 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $fe5a = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
|
||||
romsub $fe5d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
|
||||
romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
|
||||
romsub $fe6c = ABS() ; fac1 = ABS(fac1)
|
||||
romsub $fe6f = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
|
||||
romsub $fe78 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
|
||||
romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
|
||||
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
|
||||
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
|
||||
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
|
||||
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
|
||||
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
|
||||
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
|
||||
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
|
||||
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
|
||||
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
|
||||
romsub $feab = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
|
||||
romsub $feae = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
|
||||
|
||||
|
||||
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
|
||||
%asm {{
|
||||
phx
|
||||
sta P8ZP_SCRATCH_REG
|
||||
sty P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
jsr GIVAYF ; load it as signed... correct afterwards
|
||||
lda P8ZP_SCRATCH_B1
|
||||
bpl +
|
||||
lda #<_flt65536
|
||||
ldy #>_flt65536
|
||||
jsr FADD
|
||||
+ plx
|
||||
rts
|
||||
_flt65536 .byte 145,0,0,0,0 ; 65536.0
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
jmp GIVAYF ; this uses the inverse order, Y/A
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to signed word in A/Y
|
||||
%asm {{
|
||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to unsigned word in A/Y
|
||||
%asm {{
|
||||
jsr GETADR ; this uses the inverse order, Y/A
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub print_f (float value) {
|
||||
; ---- prints the floating point value (without a newline).
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_REG_X
|
||||
lda #<value
|
||||
ldy #>value
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
sta P8ZP_SCRATCH_B1
|
||||
sty P8ZP_SCRATCH_REG
|
||||
ldy #0
|
||||
- lda (P8ZP_SCRATCH_B1),y
|
||||
beq +
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
bne -
|
||||
ldx P8ZP_SCRATCH_REG_X
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
%asminclude "library:c64floats.asm", ""
|
||||
|
||||
}
|
221
compiler/res/prog8lib/cx16lib.p8
Normal file
221
compiler/res/prog8lib/cx16lib.p8
Normal file
@ -0,0 +1,221 @@
|
||||
; Prog8 definitions for the CommanderX16
|
||||
; Including memory registers, I/O registers, Basic and Kernal subroutines.
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
c64 {
|
||||
|
||||
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
|
||||
|
||||
; STROUT --> use screen.print
|
||||
; CLEARSCR -> use screen.clear_screen
|
||||
; HOMECRSR -> use screen.plot
|
||||
|
||||
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
|
||||
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
|
||||
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
|
||||
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
|
||||
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
|
||||
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 $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
|
||||
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
|
||||
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
|
||||
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
|
||||
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
|
||||
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
|
||||
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
|
||||
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
|
||||
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
|
||||
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
|
||||
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
|
||||
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
|
||||
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
|
||||
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
|
||||
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
|
||||
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
|
||||
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
|
||||
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
|
||||
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
|
||||
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
|
||||
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
|
||||
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
|
||||
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
|
||||
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
|
||||
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
|
||||
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
|
||||
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use screen.plot for a 'safe' wrapper that preserves X.
|
||||
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
|
||||
|
||||
}
|
||||
|
||||
cx16 {
|
||||
|
||||
; ---- Commander X-16 additions on top of C64 kernal routines ----
|
||||
; spelling of the names is taken from the Commander X-16 rom sources
|
||||
|
||||
; 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
|
||||
|
||||
; VERA registers
|
||||
|
||||
const uword VERA_BASE = $9F20
|
||||
&uword VERA_ADDR_L = VERA_BASE + $00
|
||||
&uword VERA_ADDR_M = VERA_BASE + $01
|
||||
&uword VERA_ADDR_H = VERA_BASE + $02
|
||||
&uword VERA_DATA0 = VERA_BASE + $03
|
||||
&uword VERA_DATA1 = VERA_BASE + $04
|
||||
&uword VERA_CTRL = VERA_BASE + $05
|
||||
&uword VERA_IEN = VERA_BASE + $06
|
||||
&uword VERA_ISR = VERA_BASE + $07
|
||||
&uword VERA_IRQ_LINE_L = VERA_BASE + $08
|
||||
&uword VERA_DC_VIDEO = VERA_BASE + $09
|
||||
&uword VERA_DC_HSCALE = VERA_BASE + $0A
|
||||
&uword VERA_DC_VSCALE = VERA_BASE + $0B
|
||||
&uword VERA_DC_BORDER = VERA_BASE + $0C
|
||||
&uword VERA_DC_HSTART = VERA_BASE + $09
|
||||
&uword VERA_DC_HSTOP = VERA_BASE + $0A
|
||||
&uword VERA_DC_VSTART = VERA_BASE + $0B
|
||||
&uword VERA_DC_VSTOP = VERA_BASE + $0C
|
||||
&uword VERA_L0_CONFIG = VERA_BASE + $0D
|
||||
&uword VERA_L0_MAPBASE = VERA_BASE + $0E
|
||||
&uword VERA_L0_TILEBASE = VERA_BASE + $0F
|
||||
&uword VERA_L0_HSCROLL_L = VERA_BASE + $10
|
||||
&uword VERA_L0_HSCROLL_H = VERA_BASE + $11
|
||||
&uword VERA_L0_VSCROLL_L = VERA_BASE + $12
|
||||
&uword VERA_L0_VSCROLL_H = VERA_BASE + $13
|
||||
&uword VERA_L1_CONFIG = VERA_BASE + $14
|
||||
&uword VERA_L1_MAPBASE = VERA_BASE + $15
|
||||
&uword VERA_L1_TILEBASE = VERA_BASE + $16
|
||||
&uword VERA_L1_HSCROLL_L = VERA_BASE + $17
|
||||
&uword VERA_L1_HSCROLL_H = VERA_BASE + $18
|
||||
&uword VERA_L1_VSCROLL_L = VERA_BASE + $19
|
||||
&uword VERA_L1_VSCROLL_H = VERA_BASE + $1A
|
||||
&uword VERA_AUDIO_CTRL = VERA_BASE + $1B
|
||||
&uword VERA_AUDIO_RATE = VERA_BASE + $1C
|
||||
&uword VERA_AUDIO_DATA = VERA_BASE + $1D
|
||||
&uword VERA_SPI_DATA = VERA_BASE + $1E
|
||||
&uword VERA_SPI_CTRL = VERA_BASE + $1F
|
||||
; VERA_PSG_BASE = $1F9C0
|
||||
; VERA_PALETTE_BASE = $1FA00
|
||||
; VERA_SPRITES_BASE = $1FC00
|
||||
|
||||
|
||||
; supported C128 additions
|
||||
romsub $ff4a = close_all()
|
||||
romsub $ff59 = lkupla()
|
||||
romsub $ff5c = lkupsa()
|
||||
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()
|
||||
romsub $ff6e = jsrfar()
|
||||
romsub $ff74 = fetch()
|
||||
romsub $ff77 = stash()
|
||||
romsub $ff7a = cmpare()
|
||||
romsub $ff7d = primm()
|
||||
|
||||
; X16 additions
|
||||
romsub $ff44 = macptr()
|
||||
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc)
|
||||
romsub $ff68 = mouse_config(ubyte shape @A, ubyte scale @X) clobbers (A, X, Y)
|
||||
romsub $ff6b = mouse_get(ubyte zpdataptr @X) clobbers(A)
|
||||
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
|
||||
|
||||
; high level graphics & fonts
|
||||
romsub $ff20 = GRAPH_init() ; uses vectors=r0
|
||||
romsub $ff23 = GRAPH_clear()
|
||||
romsub $ff26 = GRAPH_set_window() ; uses x=r0, y=r1, width=r2, height=r3
|
||||
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y)
|
||||
romsub $ff2c = GRAPH_draw_line() ; uses x1=r0, y1=r1, x2=r2, y2=r3
|
||||
romsub $ff2f = GRAPH_draw_rect(ubyte fill @Pc) ; uses x=r0, y=r1, width=r2, height=r3, cornerradius=r4
|
||||
romsub $ff32 = GRAPH_move_rect() ; uses sx=r0, sy=r1, tx=r2, ty=r3, width=r4, height=r5
|
||||
romsub $ff35 = GRAPH_draw_oval(ubyte fill @Pc) ; uses x=r0, y=r1, width=r2, height=r3
|
||||
romsub $ff38 = GRAPH_draw_image() ; uses x=r0, y=r1, ptr=r2, width=r3, height=r4
|
||||
romsub $ff3b = GRAPH_set_font() ; uses ptr=r0
|
||||
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc)
|
||||
romsub $ff41 = GRAPH_put_char(ubyte char @A) ; uses x=r0, y=r1
|
||||
|
||||
; framebuffer
|
||||
romsub $fef6 = FB_init()
|
||||
romsub $fef9 = FB_get_info() -> byte @A ; also outputs width=r0, height=r1
|
||||
romsub $fefc = FB_set_palette(ubyte index @A, ubyte bytecount @X) ; also uses pointer=r0
|
||||
romsub $feff = FB_cursor_position() ; uses x=r0, y=r1
|
||||
romsub $ff02 = FB_cursor_next_line() ; uses x=r0
|
||||
romsub $ff05 = FB_get_pixel() -> ubyte @A
|
||||
romsub $ff08 = FB_get_pixels() ; uses ptr=r0, count=r1
|
||||
romsub $ff0b = FB_set_pixel(ubyte color @A)
|
||||
romsub $ff0e = FB_set_pixels() ; uses ptr=r0, count=r1
|
||||
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X)
|
||||
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @A, ubyte color1 @X, ubyte color2 @Y) ; also uses mask=r0L
|
||||
romsub $ff17 = FB_fill_pixels(ubyte color @A) ; also uses count=r0, step=r1
|
||||
romsub $ff1a = FB_filter_pixels() ; uses ptr=r0, count=r1
|
||||
romsub $ff1d = FB_move_pixels() ; uses sx=r0, sy=r1, tx=r2, ty=r3, count=r4
|
||||
|
||||
; misc
|
||||
romsub $fef0 = sprite_set_image(ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) -> ubyte @Pc ; also uses pixels=r0, mask=r1, bpp=r2L
|
||||
romsub $fef3 = sprite_set_position(ubyte number @A) ; also uses x=r0 and y=r1
|
||||
romsub $fee4 = memory_fill(ubyte value @A) ; uses address=r0, num_bytes=r1
|
||||
romsub $fee7 = memory_copy() ; uses source=r0, target=r1, num_bytes=r2
|
||||
romsub $feea = memory_crc() ; uses address=r0, num_bytes=r1 result->r2
|
||||
romsub $feed = memory_decompress() ; uses input=r0, output=r1 result->r1
|
||||
romsub $fedb = console_init() ; uses x=r0, y=r1, width=r2, height=r3
|
||||
romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc)
|
||||
romsub $fee1 = console_get_char() -> ubyte @A
|
||||
romsub $fed8 = console_put_image() ; uses ptr=r0, width=r1, height=r2
|
||||
romsub $fed5 = console_set_paging_message() ; uses messageptr=r0
|
||||
romsub $fed2 = kbdbuf_put(ubyte key @A)
|
||||
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
|
||||
romsub $fecc = monitor()
|
||||
|
||||
|
||||
|
||||
; ---- end of kernal routines ----
|
||||
|
||||
asmsub init_system() {
|
||||
; Initializes the machine to a sane starting state.
|
||||
; Called automatically by the loader program logic.
|
||||
%asm {{
|
||||
sei
|
||||
cld
|
||||
stz $00
|
||||
stz $01
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
clc
|
||||
clv
|
||||
cli
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
314
compiler/res/prog8lib/cx16textio.p8
Normal file
314
compiler/res/prog8lib/cx16textio.p8
Normal file
@ -0,0 +1,314 @@
|
||||
; 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
|
||||
|
||||
|
||||
%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
|
||||
}}
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -4,8 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%import c64lib
|
||||
|
||||
math {
|
||||
%asminclude "library:math.asm", ""
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,8 +4,6 @@
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
%import c64lib
|
||||
|
||||
prog8_lib {
|
||||
%asminclude "library:prog8lib.asm", ""
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
1.91
|
||||
4.1
|
||||
|
@ -8,6 +8,7 @@ import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.compiler.target.cx16.CX16MachineDefinition
|
||||
import prog8.parser.ParsingFailedError
|
||||
import java.io.IOException
|
||||
import java.nio.file.FileSystems
|
||||
@ -35,12 +36,12 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
|
||||
|
||||
private fun compileMain(args: Array<String>) {
|
||||
val cli = CommandLineInterface("prog8compiler")
|
||||
val startEmulator by cli.flagArgument("-emu", "auto-start the Vice C-64 emulator after successful compilation")
|
||||
val startEmulator by cli.flagArgument("-emu", "auto-start emulator after successful compilation")
|
||||
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
|
||||
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
|
||||
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
|
||||
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
|
||||
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently only 'c64' (C64 6502 assembly) available", "c64")
|
||||
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently 'c64' and 'cx16' available", "c64")
|
||||
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
|
||||
|
||||
try {
|
||||
@ -52,7 +53,7 @@ private fun compileMain(args: Array<String>) {
|
||||
when(compilationTarget) {
|
||||
"c64" -> {
|
||||
with(CompilationTarget) {
|
||||
name = "c64"
|
||||
name = "Commodore-64"
|
||||
machine = C64MachineDefinition
|
||||
encodeString = { str, altEncoding ->
|
||||
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
@ -63,8 +64,21 @@ private fun compileMain(args: Array<String>) {
|
||||
asmGenerator = ::AsmGen
|
||||
}
|
||||
}
|
||||
"cx16" -> {
|
||||
with(CompilationTarget) {
|
||||
name = "Commander X16"
|
||||
machine = CX16MachineDefinition
|
||||
encodeString = { str, altEncoding ->
|
||||
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
}
|
||||
decodeString = { bytes, altEncoding ->
|
||||
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
|
||||
}
|
||||
asmGenerator = ::AsmGen
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
System.err.println("invalid compilation target")
|
||||
System.err.println("invalid compilation target. Available are: c64, cx16")
|
||||
exitProcess(1)
|
||||
}
|
||||
}
|
||||
@ -121,20 +135,7 @@ private fun compileMain(args: Array<String>) {
|
||||
if (compilationResult.programName.isEmpty())
|
||||
println("\nCan't start emulator because no program was assembled.")
|
||||
else if(startEmulator) {
|
||||
for(emulator in listOf("x64sc", "x64")) {
|
||||
println("\nStarting C-64 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
process=processb.start()
|
||||
} catch(x: IOException) {
|
||||
continue // try the next emulator executable
|
||||
}
|
||||
process.waitFor()
|
||||
break
|
||||
}
|
||||
CompilationTarget.machine.launchEmulator(compilationResult.programName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,6 +102,12 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
|
||||
// if the vardecl is a parameter of a subroutine, don't output it again
|
||||
val paramNames = (decl.definingScope() as? Subroutine)?.parameters?.map { it.name }
|
||||
if(paramNames!=null && decl.name in paramNames)
|
||||
return
|
||||
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR -> {}
|
||||
VarDeclType.CONST -> output("const ")
|
||||
@ -177,8 +183,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
|
||||
private fun outputStatements(statements: List<Statement>) {
|
||||
for(stmt in statements) {
|
||||
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
|
||||
continue // skip autogenerated decls (to avoid generating a newline)
|
||||
outputi("")
|
||||
stmt.accept(this)
|
||||
output("\n")
|
||||
@ -283,12 +287,19 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment) {
|
||||
assignment.target.accept(this)
|
||||
if (assignment.aug_op != null)
|
||||
output(" ${assignment.aug_op} ")
|
||||
else
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null && binExpr.left isSameAs assignment.target
|
||||
&& binExpr.operator !in setOf("and", "or", "xor")
|
||||
&& binExpr.operator !in comparisonOperators) {
|
||||
// we only support the inplace assignments of the form A = A <operator> <value>
|
||||
assignment.target.accept(this)
|
||||
output(" ${binExpr.operator}= ")
|
||||
binExpr.right.accept(this)
|
||||
} else {
|
||||
assignment.target.accept(this)
|
||||
output(" = ")
|
||||
assignment.value.accept(this)
|
||||
assignment.value.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(postIncrDecr: PostIncrDecr) {
|
||||
@ -296,20 +307,13 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
output(postIncrDecr.operator)
|
||||
}
|
||||
|
||||
override fun visit(contStmt: Continue) {
|
||||
output("continue")
|
||||
}
|
||||
|
||||
override fun visit(breakStmt: Break) {
|
||||
output("break")
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {
|
||||
output("for ")
|
||||
if(forLoop.loopRegister!=null)
|
||||
output(forLoop.loopRegister.toString())
|
||||
else
|
||||
forLoop.loopVar!!.accept(this)
|
||||
forLoop.loopVar.accept(this)
|
||||
output(" in ")
|
||||
forLoop.iterable.accept(this)
|
||||
output(" ")
|
||||
@ -323,16 +327,18 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
whileLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(foreverLoop: ForeverLoop) {
|
||||
output("forever ")
|
||||
foreverLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
output("repeat ")
|
||||
repeatLoop.iterations?.accept(this)
|
||||
output(" ")
|
||||
repeatLoop.body.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(untilLoop: UntilLoop) {
|
||||
output("do ")
|
||||
untilLoop.body.accept(this)
|
||||
output(" until ")
|
||||
repeatLoop.untilCondition.accept(this)
|
||||
untilLoop.untilCondition.accept(this)
|
||||
}
|
||||
|
||||
override fun visit(returnStmt: Return) {
|
||||
@ -348,12 +354,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
}
|
||||
|
||||
override fun visit(assignTarget: AssignTarget) {
|
||||
if(assignTarget.register!=null)
|
||||
output(assignTarget.register.toString())
|
||||
else {
|
||||
assignTarget.memoryAddress?.accept(this)
|
||||
assignTarget.identifier?.accept(this)
|
||||
}
|
||||
assignTarget.memoryAddress?.accept(this)
|
||||
assignTarget.identifier?.accept(this)
|
||||
assignTarget.arrayindexed?.accept(this)
|
||||
}
|
||||
|
||||
@ -394,10 +396,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
outputlni("}}")
|
||||
}
|
||||
|
||||
override fun visit(registerExpr: RegisterExpr) {
|
||||
output(registerExpr.register.toString())
|
||||
}
|
||||
|
||||
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||
output(builtinFunctionStatementPlaceholder.name)
|
||||
}
|
||||
@ -431,12 +429,4 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
|
||||
whenChoice.statements.accept(this)
|
||||
outputln("")
|
||||
}
|
||||
|
||||
override fun visit(structLv: StructLiteralValue) {
|
||||
outputListMembers(structLv.values.asSequence(), '{', '}')
|
||||
}
|
||||
|
||||
override fun visit(nopStatement: NopStatement) {
|
||||
output("; NOP @ ${nopStatement.position} $nopStatement")
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
@ -53,32 +52,31 @@ interface INameScope {
|
||||
|
||||
fun linkParents(parent: Node)
|
||||
|
||||
fun subScopes(): Map<String, INameScope> {
|
||||
val subscopes = mutableMapOf<String, INameScope>()
|
||||
fun subScope(name: String): INameScope? {
|
||||
for(stmt in statements) {
|
||||
when(stmt) {
|
||||
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
|
||||
is ForLoop -> subscopes[stmt.body.name] = stmt.body
|
||||
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
|
||||
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
|
||||
is ForLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is UntilLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is WhileLoop -> if(stmt.body.name==name) return stmt.body
|
||||
is BranchStatement -> {
|
||||
subscopes[stmt.truepart.name] = stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars())
|
||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
||||
if(stmt.truepart.name==name) return stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||
}
|
||||
is IfStatement -> {
|
||||
subscopes[stmt.truepart.name] = stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars())
|
||||
subscopes[stmt.elsepart.name] = stmt.elsepart
|
||||
if(stmt.truepart.name==name) return stmt.truepart
|
||||
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
|
||||
}
|
||||
is WhenStatement -> {
|
||||
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
|
||||
val scope = stmt.choices.firstOrNull { it.statements.name==name }
|
||||
if(scope!=null)
|
||||
return scope.statements
|
||||
}
|
||||
is INameScope -> subscopes[stmt.name] = stmt
|
||||
is INameScope -> if(stmt.name==name) return stmt
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
return subscopes
|
||||
return null
|
||||
}
|
||||
|
||||
fun getLabelOrVariable(name: String): Statement? {
|
||||
@ -126,7 +124,7 @@ interface INameScope {
|
||||
for(module in localContext.definingModule().program.modules) {
|
||||
var scope: INameScope? = module
|
||||
for(name in scopedName.dropLast(1)) {
|
||||
scope = scope?.subScopes()?.get(name)
|
||||
scope = scope?.subScope(name)
|
||||
if(scope==null)
|
||||
break
|
||||
}
|
||||
@ -134,7 +132,7 @@ interface INameScope {
|
||||
val result = scope.getLabelOrVariable(scopedName.last())
|
||||
if(result!=null)
|
||||
return result
|
||||
return scope.subScopes()[scopedName.last()] as Statement?
|
||||
return scope.subScope(scopedName.last()) as Statement?
|
||||
}
|
||||
}
|
||||
return null
|
||||
@ -146,7 +144,7 @@ interface INameScope {
|
||||
val result = localScope.getLabelOrVariable(scopedName[0])
|
||||
if (result != null)
|
||||
return result
|
||||
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
|
||||
val subscope = localScope.subScope(scopedName[0]) as Statement?
|
||||
if (subscope != null)
|
||||
return subscope
|
||||
// not found in this scope, look one higher up
|
||||
@ -157,6 +155,7 @@ interface INameScope {
|
||||
}
|
||||
|
||||
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
|
||||
fun containsNoVars() = statements.all { it !is VarDecl }
|
||||
fun containsNoCodeNorVars() = !containsCodeOrVars()
|
||||
|
||||
fun remove(stmt: Statement) {
|
||||
@ -176,8 +175,8 @@ interface INameScope {
|
||||
find(it.truepart)
|
||||
find(it.elsepart)
|
||||
}
|
||||
is UntilLoop -> find(it.body)
|
||||
is RepeatLoop -> find(it.body)
|
||||
is ForeverLoop -> find(it.body)
|
||||
is WhileLoop -> find(it.body)
|
||||
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
|
||||
else -> { /* do nothing */ }
|
||||
@ -188,6 +187,14 @@ interface INameScope {
|
||||
find(this)
|
||||
return result
|
||||
}
|
||||
|
||||
fun nextSibling(stmt: Statement): Statement? {
|
||||
val nextIdx = statements.indexOfFirst { it===stmt } + 1
|
||||
return if(nextIdx < statements.size)
|
||||
statements[nextIdx]
|
||||
else
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
interface IAssignable {
|
||||
@ -212,7 +219,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||
return if(mainBlocks.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
mainBlocks[0].subScopes()["start"] as Subroutine?
|
||||
mainBlocks[0].subScope("start") as Subroutine?
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,8 +238,9 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node is Module && replacement is Module)
|
||||
val idx = modules.indexOf(node)
|
||||
val idx = modules.indexOfFirst { it===node }
|
||||
modules[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,13 +265,13 @@ class Module(override val name: String,
|
||||
override fun definingScope(): INameScope = program.namespace
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node is Statement && replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
|
||||
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -306,9 +314,9 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
|
||||
}
|
||||
// lookup something from the module.
|
||||
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
|
||||
is Label, is VarDecl, is Block, is Subroutine -> stmt
|
||||
is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt
|
||||
null -> null
|
||||
else -> throw SyntaxError("wrong identifier target for $scopedName: $stmt", stmt.position)
|
||||
else -> throw SyntaxError("invalid identifier target type", stmt.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -161,14 +161,15 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
if(vardecl!=null) return vardecl
|
||||
|
||||
assignment()?.let {
|
||||
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
|
||||
return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition())
|
||||
}
|
||||
|
||||
augassignment()?.let {
|
||||
return Assignment(it.assign_target().toAst(),
|
||||
it.operator.text,
|
||||
it.expression().toAst(),
|
||||
it.toPosition())
|
||||
// replace A += X with A = A + X
|
||||
val target = it.assign_target().toAst()
|
||||
val oper = it.operator.text.substringBefore('=')
|
||||
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
|
||||
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
|
||||
}
|
||||
|
||||
postincrdecr()?.let {
|
||||
@ -205,21 +206,18 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
|
||||
val forloop = forloop()?.toAst()
|
||||
if(forloop!=null) return forloop
|
||||
|
||||
val repeatloop = repeatloop()?.toAst()
|
||||
if(repeatloop!=null) return repeatloop
|
||||
val untilloop = untilloop()?.toAst()
|
||||
if(untilloop!=null) return untilloop
|
||||
|
||||
val whileloop = whileloop()?.toAst()
|
||||
if(whileloop!=null) return whileloop
|
||||
|
||||
val foreverloop = foreverloop()?.toAst()
|
||||
if(foreverloop!=null) return foreverloop
|
||||
val repeatloop = repeatloop()?.toAst()
|
||||
if(repeatloop!=null) return repeatloop
|
||||
|
||||
val breakstmt = breakstmt()?.toAst()
|
||||
if(breakstmt!=null) return breakstmt
|
||||
|
||||
val continuestmt = continuestmt()?.toAst()
|
||||
if(continuestmt!=null) return continuestmt
|
||||
|
||||
val whenstmt = whenstmt()?.toAst()
|
||||
if(whenstmt!=null) return whenstmt
|
||||
|
||||
@ -247,7 +245,7 @@ private class AsmsubDecl(val name: String,
|
||||
val returntypes: List<DataType>,
|
||||
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||
val asmClobbers: Set<Register>)
|
||||
val asmClobbers: Set<CpuRegister>)
|
||||
|
||||
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
|
||||
val name = identifier().text
|
||||
@ -274,24 +272,43 @@ private class AsmSubroutineReturn(val type: DataType,
|
||||
val stack: Boolean,
|
||||
val position: Position)
|
||||
|
||||
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
|
||||
= this.register().asSequence().map { it.toAst() }.toSet()
|
||||
|
||||
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
|
||||
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
|
||||
= asmsub_return().map {
|
||||
val register = it.identifier()?.toAst()
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
when (val name = register.nameInSource.single()) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
|
||||
else -> throw FatalAstException("invalid register or status flag in $it")
|
||||
}
|
||||
}
|
||||
AsmSubroutineReturn(
|
||||
it.datatype().toAst(),
|
||||
registerorpair,
|
||||
statusregister,
|
||||
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
|
||||
= asmsub_param().map {
|
||||
val vardecl = it.vardecl()
|
||||
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
|
||||
AsmSubroutineParameter(vardecl.varname.text, datatype,
|
||||
it.registerorpair()?.toAst(),
|
||||
it.statusregister()?.toAst(),
|
||||
val register = it.identifier()?.toAst()
|
||||
var registerorpair: RegisterOrPair? = null
|
||||
var statusregister: Statusflag? = null
|
||||
if(register!=null) {
|
||||
when (val name = register.nameInSource.single()) {
|
||||
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
|
||||
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
|
||||
else -> throw FatalAstException("invalid register or status flag '$name'")
|
||||
}
|
||||
}
|
||||
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister,
|
||||
!it.stack?.text.isNullOrEmpty(), toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
|
||||
|
||||
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
|
||||
val void = this.VOID() != null
|
||||
val location = scoped_identifier().toAst()
|
||||
@ -350,23 +367,22 @@ private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
|
||||
}
|
||||
|
||||
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
|
||||
val register = register()?.toAst()
|
||||
val identifier = scoped_identifier()
|
||||
return when {
|
||||
register!=null -> AssignTarget(register, null, null, null, toPosition())
|
||||
identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition())
|
||||
arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition())
|
||||
directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
||||
else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition())
|
||||
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
|
||||
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
|
||||
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
|
||||
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
|
||||
}
|
||||
}
|
||||
|
||||
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase())
|
||||
private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
|
||||
val names = this.identifier().map { it.toAst().nameInSource.single() }
|
||||
return names.map { CpuRegister.valueOf(it) }.toSet()
|
||||
}
|
||||
|
||||
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
|
||||
|
||||
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
|
||||
|
||||
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
|
||||
ArrayIndex(expression().toAst(), toPosition())
|
||||
|
||||
@ -469,18 +485,11 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
|
||||
// the ConstantFold takes care of that and converts the type if needed.
|
||||
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
|
||||
}
|
||||
litval.structliteral()!=null -> {
|
||||
val values = litval.structliteral().expression().map { it.toAst() }
|
||||
StructLiteralValue(values, litval.toPosition())
|
||||
}
|
||||
else -> throw FatalAstException("invalid parsed literal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(register()!=null)
|
||||
return RegisterExpr(register().toAst(), register().toPosition())
|
||||
|
||||
if(scoped_identifier()!=null)
|
||||
return scoped_identifier().toAst()
|
||||
|
||||
@ -572,19 +581,16 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
|
||||
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
|
||||
|
||||
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
|
||||
val loopregister = register()?.toAst()
|
||||
val loopvar = identifier()?.toAst()
|
||||
val loopvar = identifier().toAst()
|
||||
val iterable = expression()!!.toAst()
|
||||
val scope =
|
||||
if(statement()!=null)
|
||||
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
|
||||
else
|
||||
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
|
||||
return ForLoop(loopregister, loopvar, iterable, scope, toPosition())
|
||||
return ForLoop(loopvar, iterable, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
|
||||
|
||||
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
|
||||
|
||||
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
||||
@ -595,19 +601,20 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
|
||||
return WhileLoop(condition, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.ForeverloopContext.toAst(): ForeverLoop {
|
||||
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
val iterations = expression()?.toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return ForeverLoop(scope, toPosition())
|
||||
return RepeatLoop(iterations, scope, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
|
||||
private fun prog8Parser.UntilloopContext.toAst(): UntilLoop {
|
||||
val untilCondition = expression().toAst()
|
||||
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
|
||||
val scope = AnonymousScope(statements, statement_block()?.toPosition()
|
||||
?: statement().toPosition())
|
||||
return RepeatLoop(scope, untilCondition, toPosition())
|
||||
return UntilLoop(scope, untilCondition, toPosition())
|
||||
}
|
||||
|
||||
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
|
||||
|
@ -21,10 +21,9 @@ enum class DataType {
|
||||
STRUCT; // pass by reference
|
||||
|
||||
/**
|
||||
* is the type assignable to the given other type?
|
||||
* is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
|
||||
*/
|
||||
infix fun isAssignableTo(targetType: DataType) =
|
||||
// what types are assignable to others without loss of precision?
|
||||
when(this) {
|
||||
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
|
||||
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
|
||||
@ -58,13 +57,13 @@ enum class DataType {
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes -> 2
|
||||
FLOAT -> CompilationTarget.machine.FLOAT_MEM_SIZE
|
||||
in PassByReferenceDatatypes -> 2
|
||||
in PassByReferenceDatatypes -> CompilationTarget.machine.POINTER_MEM_SIZE
|
||||
else -> -9999999
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class Register {
|
||||
enum class CpuRegister {
|
||||
A,
|
||||
X,
|
||||
Y
|
||||
@ -76,14 +75,23 @@ enum class RegisterOrPair {
|
||||
Y,
|
||||
AX,
|
||||
AY,
|
||||
XY
|
||||
XY;
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
}
|
||||
|
||||
} // only used in parameter and return value specs in asm subroutines
|
||||
|
||||
enum class Statusflag {
|
||||
Pc,
|
||||
Pz,
|
||||
Pv,
|
||||
Pn
|
||||
Pn;
|
||||
|
||||
companion object {
|
||||
val names by lazy { values().map { it.toString()} }
|
||||
}
|
||||
}
|
||||
|
||||
enum class BranchCondition {
|
||||
@ -150,7 +158,9 @@ object ParentSentinel : Node {
|
||||
override val position = Position("<<sentinel>>", 0, 0, 0)
|
||||
override var parent: Node = this
|
||||
override fun linkParents(parent: Node) {}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
replacement.parent = this
|
||||
}
|
||||
}
|
||||
|
||||
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
|
||||
|
@ -2,7 +2,7 @@ package prog8.ast.base
|
||||
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
|
||||
class FatalAstException (override var message: String) : Exception(message)
|
||||
open class FatalAstException (override var message: String) : Exception(message)
|
||||
|
||||
open class AstException (override var message: String) : Exception(message)
|
||||
|
||||
|
@ -4,8 +4,7 @@ import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.*
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.target.AsmVariableAndReturnsPreparer
|
||||
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
|
||||
import prog8.compiler.BeforeAsmGenerationAstChanger
|
||||
|
||||
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
|
||||
@ -13,19 +12,16 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: Err
|
||||
checker.visit(this)
|
||||
}
|
||||
|
||||
internal fun Program.prepareAsmVariablesAndReturns(errors: ErrorReporter) {
|
||||
val fixer = AsmVariableAndReturnsPreparer(this, errors)
|
||||
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
|
||||
val fixer = BeforeAsmGenerationAstChanger(this, errors)
|
||||
fixer.visit(this)
|
||||
fixer.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.reorderStatements() {
|
||||
val initvalueCreator = AddressOfInserter(this)
|
||||
initvalueCreator.visit(this)
|
||||
initvalueCreator.applyModifications()
|
||||
|
||||
val checker = StatementReorderer(this)
|
||||
checker.visit(this)
|
||||
val reorder = StatementReorderer(this)
|
||||
reorder.visit(this)
|
||||
reorder.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
@ -34,6 +30,11 @@ internal fun Program.addTypecasts(errors: ErrorReporter) {
|
||||
caster.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.verifyFunctionArgTypes() {
|
||||
val fixer = VerifyFunctionArgTypes(this)
|
||||
fixer.visit(this)
|
||||
}
|
||||
|
||||
internal fun Module.checkImportedValid() {
|
||||
val imr = ImportedModuleDirectiveRemover()
|
||||
imr.visit(this, this.parent)
|
||||
@ -47,21 +48,23 @@ internal fun Program.checkRecursion(errors: ErrorReporter) {
|
||||
}
|
||||
|
||||
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
|
||||
val checker = AstIdentifiersChecker(this, errors)
|
||||
checker.visit(this)
|
||||
|
||||
val checker2 = AstIdentifiersChecker(this, errors)
|
||||
checker2.visit(this)
|
||||
|
||||
if(errors.isEmpty()) {
|
||||
val transforms = AstVariousTransforms(this)
|
||||
transforms.visit(this)
|
||||
transforms.applyModifications()
|
||||
}
|
||||
|
||||
if (modules.map { it.name }.toSet().size != modules.size) {
|
||||
throw FatalAstException("modules should all be unique")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Program.makeForeverLoops() {
|
||||
val checker = ForeverLoopsMaker()
|
||||
checker.visit(this)
|
||||
checker.applyModifications()
|
||||
}
|
||||
|
||||
internal fun Program.removeNopsFlattenAnonScopes() {
|
||||
val flattener = FlattenAnonymousScopesAndNopRemover()
|
||||
flattener.visit(this)
|
||||
internal fun Program.variousCleanups() {
|
||||
val process = VariousCleanups()
|
||||
process.visit(this)
|
||||
process.applyModifications()
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ import prog8.ast.*
|
||||
import prog8.ast.antlr.escape
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.CannotEvaluateException
|
||||
import prog8.functions.NotConstArgumentException
|
||||
import prog8.functions.builtinFunctionReturnType
|
||||
import java.util.*
|
||||
@ -16,35 +16,52 @@ import kotlin.math.abs
|
||||
|
||||
|
||||
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
|
||||
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
|
||||
|
||||
sealed class Expression: Node {
|
||||
abstract fun constValue(program: Program): NumericLiteralValue?
|
||||
abstract fun accept(visitor: IAstModifyingVisitor): Expression
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
|
||||
abstract fun referencesIdentifiers(vararg name: String): Boolean
|
||||
abstract fun inferType(program: Program): InferredTypes.InferredType
|
||||
|
||||
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
|
||||
|
||||
infix fun isSameAs(other: Expression): Boolean {
|
||||
if(this===other)
|
||||
return true
|
||||
when(this) {
|
||||
is RegisterExpr ->
|
||||
return (other is RegisterExpr && other.register==register)
|
||||
return when(this) {
|
||||
is IdentifierReference ->
|
||||
return (other is IdentifierReference && other.nameInSource==nameInSource)
|
||||
(other is IdentifierReference && other.nameInSource==nameInSource)
|
||||
is PrefixExpression ->
|
||||
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
||||
(other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
|
||||
is BinaryExpression ->
|
||||
return (other is BinaryExpression && other.operator==operator
|
||||
(other is BinaryExpression && other.operator==operator
|
||||
&& other.left isSameAs left
|
||||
&& other.right isSameAs right)
|
||||
is ArrayIndexedExpression -> {
|
||||
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
||||
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
|
||||
&& other.arrayspec.index isSameAs arrayspec.index)
|
||||
}
|
||||
else -> return other==this
|
||||
is DirectMemoryRead -> {
|
||||
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
|
||||
}
|
||||
is TypecastExpression -> {
|
||||
(other is TypecastExpression && other.implicit==implicit && other.type==type && other.expression isSameAs expression)
|
||||
}
|
||||
is AddressOf -> {
|
||||
(other is AddressOf && other.identifier.nameInSource == identifier.nameInSource)
|
||||
}
|
||||
is RangeExpr -> {
|
||||
(other is RangeExpr && other.from==from && other.to==to && other.step==step)
|
||||
}
|
||||
is FunctionCall -> {
|
||||
(other is FunctionCall && other.target.nameInSource == target.nameInSource
|
||||
&& other.args.size == args.size
|
||||
&& other.args.zip(args).all { it.first isSameAs it.second } )
|
||||
}
|
||||
else -> other==this
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,10 +78,10 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(node === expression && replacement is Expression)
|
||||
expression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -112,6 +129,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
node===right -> right = replacement
|
||||
else -> throw FatalAstException("invalid replace, no child $node")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -121,7 +139,6 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
|
||||
// binary expression should actually have been optimized away into a single value, before const value was requested...
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -231,10 +248,10 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
|
||||
node===arrayspec.index -> arrayspec.index = replacement as Expression
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -268,9 +285,9 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===expression)
|
||||
expression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -278,9 +295,11 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
|
||||
override fun constValue(program: Program): NumericLiteralValue? {
|
||||
val cv = expression.constValue(program) ?: return null
|
||||
return cv.cast(type)
|
||||
// val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
|
||||
// return LiteralValue.fromNumber(value.numericValue(), value.type, position).cast(type)
|
||||
val cast = cv.cast(type)
|
||||
return if(cast.isValid)
|
||||
cast.valueOrZero()
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -299,12 +318,12 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is IdentifierReference && node===identifier)
|
||||
identifier = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
}
|
||||
@ -320,9 +339,9 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===addressExpression)
|
||||
addressExpression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -382,7 +401,6 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun constValue(program: Program) = this
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -400,88 +418,66 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
|
||||
|
||||
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
|
||||
|
||||
fun cast(targettype: DataType): NumericLiteralValue {
|
||||
class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) {
|
||||
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY)
|
||||
}
|
||||
|
||||
fun cast(targettype: DataType): CastValue {
|
||||
if(type==targettype)
|
||||
return this
|
||||
return CastValue(true, this)
|
||||
val numval = number.toDouble()
|
||||
when(type) {
|
||||
DataType.UBYTE -> {
|
||||
if(targettype== DataType.BYTE && numval <= 127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.WORD || targettype== DataType.UWORD)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(targettype== DataType.UBYTE && numval >= 0)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UWORD && numval >= 0)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.WORD)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(targettype== DataType.BYTE && numval <= 127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UBYTE && numval <= 255)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.WORD && numval <= 32767)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if(targettype== DataType.UWORD && numval >=0)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if(targettype== DataType.FLOAT)
|
||||
return NumericLiteralValue(targettype, number.toDouble(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
|
||||
return NumericLiteralValue(targettype, number.toShort(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
|
||||
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
|
||||
return NumericLiteralValue(targettype, number.toInt(), position)
|
||||
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
throw ExpressionError("can't cast $type into $targettype", position)
|
||||
}
|
||||
}
|
||||
|
||||
class StructLiteralValue(var values: List<Expression>,
|
||||
override val position: Position): Expression() {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
values.forEach { it.linkParents(this) }
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
|
||||
|
||||
override fun toString(): String {
|
||||
return "struct{ ${values.joinToString(", ")} }"
|
||||
return CastValue(false, null)
|
||||
}
|
||||
}
|
||||
|
||||
@ -504,7 +500,6 @@ class StringLiteralValue(val value: String,
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = false
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -533,18 +528,18 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
val idx = value.indexOf(node)
|
||||
val idx = value.indexOfFirst { it===node }
|
||||
value[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String = "$value"
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isUnknown) type else guessDatatype(program)
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) type else guessDatatype(program)
|
||||
|
||||
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
|
||||
override fun hashCode(): Int = Objects.hash(value, type)
|
||||
@ -592,14 +587,14 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
|
||||
if(num==null) {
|
||||
// an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted
|
||||
if (elementType != DataType.UWORD || it !is AddressOf)
|
||||
return null
|
||||
return null // can't cast a value of the array, abort
|
||||
it
|
||||
} else {
|
||||
try {
|
||||
num.cast(elementType)
|
||||
} catch(x: ExpressionError) {
|
||||
return null
|
||||
}
|
||||
val cast = num.cast(elementType)
|
||||
if(cast.isValid)
|
||||
cast.valueOrZero()
|
||||
else
|
||||
return null // can't cast a value of the array, abort
|
||||
}
|
||||
}.toTypedArray()
|
||||
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
|
||||
@ -629,10 +624,10 @@ class RangeExpr(var from: Expression,
|
||||
step===node -> step=replacement
|
||||
else -> throw FatalAstException("invalid replacement")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
@ -647,7 +642,14 @@ class RangeExpr(var from: Expression,
|
||||
fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
|
||||
fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W)
|
||||
fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B)
|
||||
else -> InferredTypes.knownFor(DataType.ARRAY_UB)
|
||||
else -> {
|
||||
val fdt = fromDt.typeOrElse(DataType.STRUCT)
|
||||
val tdt = toDt.typeOrElse(DataType.STRUCT)
|
||||
if(fdt largerThan tdt)
|
||||
InferredTypes.knownFor(ElementArrayTypes.getValue(fdt))
|
||||
else
|
||||
InferredTypes.knownFor(ElementArrayTypes.getValue(tdt))
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun toString(): String {
|
||||
@ -700,30 +702,6 @@ internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||
}
|
||||
}
|
||||
|
||||
class RegisterExpr(val register: Register, override val position: Position) : Expression(), IAssignable {
|
||||
override lateinit var parent: Node
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
override fun constValue(program: Program): NumericLiteralValue? = null
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
|
||||
override fun toString(): String {
|
||||
return "RegisterExpr(register=$register, pos=$position)"
|
||||
}
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
|
||||
}
|
||||
|
||||
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
|
||||
override lateinit var parent: Node
|
||||
|
||||
@ -736,6 +714,9 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
|
||||
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
|
||||
|
||||
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
|
||||
override fun hashCode() = nameInSource.hashCode()
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
@ -760,18 +741,16 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
return "IdentifierRef($nameInSource)"
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
|
||||
|
||||
override fun inferType(program: Program): InferredTypes.InferredType {
|
||||
val targetStmt = targetStatement(program.namespace)
|
||||
return if(targetStmt is VarDecl) {
|
||||
InferredTypes.knownFor(targetStmt.datatype)
|
||||
} else {
|
||||
InferredTypes.InferredType.unknown()
|
||||
return when (val targetStmt = targetStatement(program.namespace)) {
|
||||
is VarDecl -> InferredTypes.knownFor(targetStmt.datatype)
|
||||
is StructDecl -> InferredTypes.knownFor(DataType.STRUCT)
|
||||
else -> InferredTypes.InferredType.unknown()
|
||||
}
|
||||
}
|
||||
|
||||
@ -804,9 +783,10 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
if(node===target)
|
||||
target=replacement as IdentifierReference
|
||||
else {
|
||||
val idx = args.indexOf(node)
|
||||
val idx = args.indexOfFirst { it===node }
|
||||
args[idx] = replacement as Expression
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun constValue(program: Program) = constValue(program, true)
|
||||
@ -839,13 +819,16 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
// const-evaluating the builtin function call failed.
|
||||
return null
|
||||
}
|
||||
catch(x: CannotEvaluateException) {
|
||||
// const-evaluating the builtin function call failed.
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "FunctionCall(target=$target, pos=$position)"
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
|
||||
|
||||
|
@ -1,93 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.IterableDatatypes
|
||||
import prog8.ast.base.PassByReferenceDatatypes
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Statement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.FSignature
|
||||
|
||||
|
||||
internal class AddressOfInserter(val program: Program): AstWalker() {
|
||||
// Insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
// TODO join this into the StatementReorderer?
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
var parentStatement: Node = functionCall
|
||||
while(parentStatement !is Statement)
|
||||
parentStatement = parentStatement.parent
|
||||
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(targetStatement!=null) {
|
||||
return addAddressOfExprIfNeeded(targetStatement, functionCall.args, functionCall)
|
||||
} else {
|
||||
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
|
||||
if(builtinFunc!=null)
|
||||
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.args, functionCall)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
// insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(targetStatement!=null) {
|
||||
return addAddressOfExprIfNeeded(targetStatement, functionCallStatement.args, functionCallStatement)
|
||||
} else {
|
||||
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
|
||||
if(builtinFunc!=null)
|
||||
return addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.args, functionCallStatement)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
|
||||
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
|
||||
val replacements = mutableListOf<IAstModification>()
|
||||
for(argparam in subroutine.parameters.withIndex().zip(args)) {
|
||||
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type == DataType.STR) {
|
||||
if(argparam.second is AddressOf)
|
||||
continue
|
||||
val idref = argparam.second as? IdentifierReference
|
||||
if(idref!=null) {
|
||||
val variable = idref.targetVarDecl(program.namespace)
|
||||
if(variable!=null && variable.datatype in IterableDatatypes) {
|
||||
replacements += IAstModification.ReplaceNode(
|
||||
args[argparam.first.index],
|
||||
AddressOf(idref, idref.position),
|
||||
parent as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return replacements
|
||||
}
|
||||
|
||||
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FSignature, args: MutableList<Expression>, parent: IFunctionCall): Iterable<IAstModification> {
|
||||
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
||||
val replacements = mutableListOf<IAstModification>()
|
||||
for(arg in args.withIndex().zip(signature.parameters)) {
|
||||
val argvalue = arg.first.value
|
||||
val argDt = argvalue.inferType(program)
|
||||
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
|
||||
if(argvalue !is IdentifierReference)
|
||||
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
|
||||
replacements += IAstModification.ReplaceNode(
|
||||
args[arg.first.index],
|
||||
AddressOf(argvalue, argvalue.position),
|
||||
parent as Node)
|
||||
}
|
||||
}
|
||||
return replacements
|
||||
}
|
||||
}
|
@ -22,10 +22,10 @@ internal class AstChecker(private val program: Program,
|
||||
if(mainBlocks.size>1)
|
||||
errors.err("more than one 'main' block", mainBlocks[0].position)
|
||||
if(mainBlocks.isEmpty())
|
||||
errors.err("there is no 'main' block", program.position)
|
||||
errors.err("there is no 'main' block", program.modules.firstOrNull()?.position ?: program.position)
|
||||
|
||||
for(mainBlock in mainBlocks) {
|
||||
val startSub = mainBlock.subScopes()["start"] as? Subroutine
|
||||
val startSub = mainBlock.subScope("start") as? Subroutine
|
||||
if (startSub == null) {
|
||||
errors.err("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position)
|
||||
} else {
|
||||
@ -58,7 +58,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(irqBlocks.size>1)
|
||||
errors.err("more than one 'irq' block", irqBlocks[0].position)
|
||||
for(irqBlock in irqBlocks) {
|
||||
val irqSub = irqBlock.subScopes()["irq"] as? Subroutine
|
||||
val irqSub = irqBlock.subScope("irq") as? Subroutine
|
||||
if (irqSub != null) {
|
||||
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
|
||||
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
|
||||
@ -110,48 +110,57 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop) {
|
||||
if(forLoop.body.containsNoCodeNorVars())
|
||||
errors.warn("for loop body is empty", forLoop.position)
|
||||
|
||||
val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE)
|
||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
|
||||
errors.err("can only loop over an iterable type", forLoop.position)
|
||||
} else {
|
||||
if (forLoop.loopRegister != null) {
|
||||
// loop register
|
||||
if (iterableDt != DataType.ARRAY_UB && iterableDt != DataType.ARRAY_B && iterableDt != DataType.STR)
|
||||
errors.err("register can only loop over bytes", forLoop.position)
|
||||
if(forLoop.loopRegister!=Register.A)
|
||||
errors.err("it's only possible to use A as a loop register", forLoop.position)
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)
|
||||
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
|
||||
errors.err("for loop requires a variable to loop with", forLoop.position)
|
||||
} else {
|
||||
// loop variable
|
||||
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
|
||||
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
|
||||
errors.err("for loop requires a variable to loop with", forLoop.position)
|
||||
} else {
|
||||
when (loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
|
||||
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
|
||||
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
|
||||
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
|
||||
errors.err("byte loop variable can only loop over bytes", forLoop.position)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.WORD &&
|
||||
iterableDt != DataType.ARRAY_B && iterableDt!= DataType.ARRAY_W)
|
||||
errors.err("word loop variable can only loop over bytes or words", forLoop.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
errors.err("for loop only supports integers", forLoop.position)
|
||||
}
|
||||
else -> errors.err("loop variable must be numeric type", forLoop.position)
|
||||
|
||||
fun checkLoopRangeValues() {
|
||||
|
||||
}
|
||||
|
||||
when (loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
|
||||
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
|
||||
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
|
||||
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
|
||||
errors.err("byte loop variable can only loop over bytes", forLoop.position)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.WORD &&
|
||||
iterableDt != DataType.ARRAY_B && iterableDt!= DataType.ARRAY_W)
|
||||
errors.err("word loop variable can only loop over bytes or words", forLoop.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
errors.err("for loop only supports integers", forLoop.position)
|
||||
}
|
||||
else -> errors.err("loop variable must be numeric type", forLoop.position)
|
||||
}
|
||||
if(errors.isEmpty()) {
|
||||
// check loop range values
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
val from = range.from as? NumericLiteralValue
|
||||
val to = range.to as? NumericLiteralValue
|
||||
if(from != null)
|
||||
checkValueTypeAndRange(loopvar.datatype, from)
|
||||
else if(!range.from.inferType(program).istype(loopvar.datatype))
|
||||
errors.err("range start value is incompatible with loop variable type", range.position)
|
||||
if(to != null)
|
||||
checkValueTypeAndRange(loopvar.datatype, to)
|
||||
else if(!range.to.inferType(program).istype(loopvar.datatype))
|
||||
errors.err("range end value is incompatible with loop variable type", range.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -260,27 +269,27 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
val regCounts = mutableMapOf<Register, Int>().withDefault { 0 }
|
||||
val regCounts = mutableMapOf<CpuRegister, Int>().withDefault { 0 }
|
||||
val statusflagCounts = mutableMapOf<Statusflag, Int>().withDefault { 0 }
|
||||
fun countRegisters(from: Iterable<RegisterOrStatusflag>) {
|
||||
regCounts.clear()
|
||||
statusflagCounts.clear()
|
||||
for(p in from) {
|
||||
when(p.registerOrPair) {
|
||||
RegisterOrPair.A -> regCounts[Register.A]=regCounts.getValue(Register.A)+1
|
||||
RegisterOrPair.X -> regCounts[Register.X]=regCounts.getValue(Register.X)+1
|
||||
RegisterOrPair.Y -> regCounts[Register.Y]=regCounts.getValue(Register.Y)+1
|
||||
RegisterOrPair.A -> regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
|
||||
RegisterOrPair.X -> regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
|
||||
RegisterOrPair.Y -> regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
|
||||
RegisterOrPair.AX -> {
|
||||
regCounts[Register.A]=regCounts.getValue(Register.A)+1
|
||||
regCounts[Register.X]=regCounts.getValue(Register.X)+1
|
||||
regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
|
||||
regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
regCounts[Register.A]=regCounts.getValue(Register.A)+1
|
||||
regCounts[Register.Y]=regCounts.getValue(Register.Y)+1
|
||||
regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
|
||||
regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
regCounts[Register.X]=regCounts.getValue(Register.X)+1
|
||||
regCounts[Register.Y]=regCounts.getValue(Register.Y)+1
|
||||
regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
|
||||
regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
|
||||
}
|
||||
null ->
|
||||
if(p.statusflag!=null)
|
||||
@ -316,26 +325,19 @@ internal class AstChecker(private val program: Program,
|
||||
} else {
|
||||
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
|
||||
// Instead, their reference (address) should be passed (as an UWORD).
|
||||
// The language has no typed pointers at this time.
|
||||
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
|
||||
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword for their address, or access the variable from the outer scope directly.")
|
||||
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
|
||||
}
|
||||
}
|
||||
|
||||
visitStatements(subroutine.statements)
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop) {
|
||||
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
|
||||
if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", repeatLoop.untilCondition.position)
|
||||
super.visit(repeatLoop)
|
||||
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)
|
||||
super.visit(untilLoop)
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop) {
|
||||
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
|
||||
errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
|
||||
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
|
||||
errors.err("condition value should be an integer type", whileLoop.condition.position)
|
||||
super.visit(whileLoop)
|
||||
@ -361,9 +363,9 @@ internal class AstChecker(private val program: Program,
|
||||
if(targetIdent!=null) {
|
||||
val targetVar = targetIdent.targetVarDecl(program.namespace)
|
||||
if(targetVar?.struct != null) {
|
||||
val sourceStructLv = assignment.value as? StructLiteralValue
|
||||
val sourceStructLv = assignment.value as? ArrayLiteralValue
|
||||
if (sourceStructLv != null) {
|
||||
if (sourceStructLv.values.size != targetVar.struct?.numberOfElements)
|
||||
if (sourceStructLv.value.size != targetVar.struct?.numberOfElements)
|
||||
errors.err("number of elements doesn't match struct definition", sourceStructLv.position)
|
||||
} else {
|
||||
val sourceIdent = assignment.value as? IdentifierReference
|
||||
@ -378,8 +380,18 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(assignment.value.inferType(program) != assignment.target.inferType(program, assignment))
|
||||
errors.err("assignment value is of different type as the target", assignment.value.position)
|
||||
val targetDt = assignment.target.inferType(program, assignment)
|
||||
if(assignment.value.inferType(program) != targetDt) {
|
||||
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
|
||||
errors.err("cannot assign value to string or array", assignment.value.position)
|
||||
else
|
||||
errors.err("value's type doesn't match target", assignment.value.position)
|
||||
}
|
||||
|
||||
if(assignment.value is TypecastExpression) {
|
||||
if(assignment.isAugmentable && targetDt.istype(DataType.FLOAT))
|
||||
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
|
||||
}
|
||||
|
||||
super.visit(assignment)
|
||||
}
|
||||
@ -397,8 +409,7 @@ internal class AstChecker(private val program: Program,
|
||||
val targetIdentifier = assignTarget.identifier
|
||||
if (targetIdentifier != null) {
|
||||
val targetName = targetIdentifier.nameInSource
|
||||
val targetSymbol = program.namespace.lookup(targetName, assignment)
|
||||
when (targetSymbol) {
|
||||
when (val targetSymbol = program.namespace.lookup(targetName, assignment)) {
|
||||
null -> {
|
||||
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
|
||||
return
|
||||
@ -423,9 +434,6 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
if (assignment is Assignment) {
|
||||
|
||||
if (assignment.aug_op != null)
|
||||
throw FatalAstException("augmented assignment should have been converted into normal assignment")
|
||||
|
||||
val targetDatatype = assignTarget.inferType(program, assignment)
|
||||
if (targetDatatype.isKnown) {
|
||||
val constVal = assignment.value.constValue(program)
|
||||
@ -454,7 +462,9 @@ internal class AstChecker(private val program: Program,
|
||||
if(variable==null)
|
||||
errors.err("pointer-of operand must be the name of a heap variable", addressOf.position)
|
||||
else {
|
||||
if(variable.datatype !in ArrayDatatypes && variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
|
||||
if(variable.datatype !in ArrayDatatypes
|
||||
&& variable.type!=VarDeclType.MEMORY
|
||||
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
|
||||
errors.err("invalid pointer-of operand type", addressOf.position)
|
||||
}
|
||||
super.visit(addressOf)
|
||||
@ -512,29 +522,21 @@ internal class AstChecker(private val program: Program,
|
||||
|
||||
when(decl.value) {
|
||||
null -> {
|
||||
// a vardecl without an initial value, don't bother with the rest
|
||||
return super.visit(decl)
|
||||
// a vardecl without an initial value, don't bother with it
|
||||
}
|
||||
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value")
|
||||
is StringLiteralValue -> {
|
||||
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
|
||||
}
|
||||
is ArrayLiteralValue -> {
|
||||
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
|
||||
checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
|
||||
}
|
||||
is StructLiteralValue -> {
|
||||
if(decl.datatype==DataType.STRUCT) {
|
||||
val struct = decl.struct!!
|
||||
val structLv = decl.value as StructLiteralValue
|
||||
if(struct.numberOfElements != structLv.values.size) {
|
||||
val structLv = decl.value as ArrayLiteralValue
|
||||
if(struct.numberOfElements != structLv.value.size) {
|
||||
errors.err("struct value has incorrect number of elements", structLv.position)
|
||||
return
|
||||
}
|
||||
for(value in structLv.values.zip(struct.statements)) {
|
||||
for(value in structLv.value.zip(struct.statements)) {
|
||||
val memberdecl = value.second as VarDecl
|
||||
val constValue = value.first.constValue(program)
|
||||
if(constValue==null) {
|
||||
@ -548,9 +550,13 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errors.err("struct literal is wrong type to initialize this variable", decl.value!!.position)
|
||||
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
|
||||
checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
|
||||
}
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
|
||||
}
|
||||
else -> {
|
||||
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!.javaClass.simpleName}")
|
||||
super.visit(decl)
|
||||
@ -560,7 +566,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
VarDeclType.MEMORY -> {
|
||||
if(decl.arraysize!=null) {
|
||||
val arraySize = decl.arraysize!!.size() ?: 1
|
||||
val arraySize = decl.arraysize!!.constIndex() ?: 1
|
||||
when(decl.datatype) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB ->
|
||||
if(arraySize > 256)
|
||||
@ -587,8 +593,38 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR && !declValue.inferType(program).istype(decl.datatype))
|
||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR) {
|
||||
if(decl.datatype==DataType.STRUCT) {
|
||||
val valueIdt = declValue.inferType(program)
|
||||
if(valueIdt.isUnknown)
|
||||
throw AstException("invalid value type")
|
||||
val valueDt = valueIdt.typeOrElse(DataType.STRUCT)
|
||||
if(valueDt !in ArrayDatatypes)
|
||||
err("initialisation of struct should be with array value", declValue.position)
|
||||
} else if (!declValue.inferType(program).istype(decl.datatype)) {
|
||||
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
|
||||
}
|
||||
}
|
||||
|
||||
// array length limits
|
||||
if(decl.isArray) {
|
||||
val length = decl.arraysize!!.constIndex() ?: 1
|
||||
when (decl.datatype) {
|
||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
if(length==0 || length>256)
|
||||
err("string and byte array length must be 1-256")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
if(length==0 || length>128)
|
||||
err("word array length must be 1-128")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
if(length==0 || length>51)
|
||||
err("float array length must be 1-51")
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
super.visit(decl)
|
||||
}
|
||||
@ -704,12 +740,20 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
override fun visit(expr: PrefixExpression) {
|
||||
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if(expr.operator=="-") {
|
||||
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
|
||||
errors.err("can only take negative of a signed number type", expr.position)
|
||||
}
|
||||
}
|
||||
else if(expr.operator == "not") {
|
||||
if(dt !in IntegerDatatypes)
|
||||
errors.err("can only use boolean not on integer types", expr.position)
|
||||
}
|
||||
else if(expr.operator == "~") {
|
||||
if(dt !in IntegerDatatypes)
|
||||
errors.err("can only use bitwise invert on integer types", expr.position)
|
||||
}
|
||||
super.visit(expr)
|
||||
}
|
||||
|
||||
@ -735,7 +779,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
"**" -> {
|
||||
if(leftDt in IntegerDatatypes)
|
||||
errors.err("power operator requires floating point", expr.position)
|
||||
errors.err("power operator requires floating point operands", expr.position)
|
||||
}
|
||||
"and", "or", "xor" -> {
|
||||
// only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1)
|
||||
@ -751,12 +795,6 @@ internal class AstChecker(private val program: Program,
|
||||
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
|
||||
errors.err("bitwise operator can only be used on integer operands", expr.right.position)
|
||||
}
|
||||
"<<", ">>" -> {
|
||||
// for now, bit-shifts can only shift by a constant number
|
||||
val constRight = expr.right.constValue(program)
|
||||
if(constRight==null)
|
||||
errors.err("bit-shift can only be done by a constant number (for now)", expr.right.position)
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt !in NumericDatatypes)
|
||||
@ -823,6 +861,10 @@ internal class AstChecker(private val program: Program,
|
||||
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
|
||||
}
|
||||
|
||||
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program)
|
||||
if(error!=null)
|
||||
errors.err(error, functionCall.args.first().position)
|
||||
|
||||
super.visit(functionCall)
|
||||
}
|
||||
|
||||
@ -845,12 +887,18 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
if(functionCallStatement.target.nameInSource.last() in setOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
|
||||
// in-place modification, can't be done on literals
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("can't use that as argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
|
||||
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
|
||||
}
|
||||
}
|
||||
|
||||
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, functionCallStatement.definingScope(), program)
|
||||
if(error!=null) {
|
||||
errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position)
|
||||
}
|
||||
|
||||
super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
@ -859,77 +907,35 @@ internal class AstChecker(private val program: Program,
|
||||
errors.err("cannot use arguments when calling a label", position)
|
||||
|
||||
if(target is BuiltinFunctionStatementPlaceholder) {
|
||||
// it's a call to a builtin function.
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
if(args.size!=func.parameters.size)
|
||||
errors.err("invalid number of arguments", position)
|
||||
else {
|
||||
val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
|
||||
for (arg in args.withIndex().zip(func.parameters)) {
|
||||
val argDt=arg.first.value.inferType(program)
|
||||
if (argDt.isKnown
|
||||
&& !(argDt.typeOrElse(DataType.STRUCT) isAssignableTo arg.second.possibleDatatypes)
|
||||
&& (argDt.typeOrElse(DataType.STRUCT) != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) {
|
||||
errors.err("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position)
|
||||
}
|
||||
if(target.name=="swap") {
|
||||
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
|
||||
val dt1 = args[0].inferType(program)
|
||||
val dt2 = args[1].inferType(program)
|
||||
if (dt1 != dt2)
|
||||
errors.err("swap requires 2 args of identical type", position)
|
||||
else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
|
||||
errors.err("swap requires 2 variables, not constant value(s)", position)
|
||||
else if(args[0] isSameAs args[1])
|
||||
errors.err("swap should have 2 different args", position)
|
||||
else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
|
||||
errors.err("swap requires args of numerical type", position)
|
||||
}
|
||||
else if(target.name=="all" || target.name=="any") {
|
||||
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
if(target.name=="swap") {
|
||||
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
|
||||
val dt1 = args[0].inferType(program)
|
||||
val dt2 = args[1].inferType(program)
|
||||
if (dt1 != dt2)
|
||||
errors.err("swap requires 2 args of identical type", position)
|
||||
else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
|
||||
errors.err("swap requires 2 variables, not constant value(s)", position)
|
||||
else if(args[0] isSameAs args[1])
|
||||
errors.err("swap should have 2 different args", position)
|
||||
else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
|
||||
errors.err("swap requires args of numerical type", position)
|
||||
}
|
||||
else if(target.name=="all" || target.name=="any") {
|
||||
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
|
||||
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
|
||||
}
|
||||
}
|
||||
} else if(target is Subroutine) {
|
||||
if(args.size!=target.parameters.size)
|
||||
errors.err("invalid number of arguments", position)
|
||||
else {
|
||||
if(target.regXasResult())
|
||||
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
|
||||
if(target.isAsmSubroutine) {
|
||||
for (arg in args.withIndex().zip(target.parameters)) {
|
||||
val argIDt = arg.first.value.inferType(program)
|
||||
if(!argIDt.isKnown) {
|
||||
if (!argIDt.isKnown)
|
||||
return
|
||||
}
|
||||
val argDt=argIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!(argDt isAssignableTo arg.second.type)) {
|
||||
// for asm subroutines having STR param it's okay to provide a UWORD (address value)
|
||||
if(!(target.isAsmSubroutine && arg.second.type == DataType.STR && argDt == DataType.UWORD))
|
||||
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position)
|
||||
}
|
||||
|
||||
if(target.isAsmSubroutine) {
|
||||
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
|
||||
if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
|
||||
errors.warn("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position)
|
||||
}
|
||||
|
||||
// check if the argument types match the register(pairs)
|
||||
val asmParamReg = target.asmParameterRegisters[arg.first.index]
|
||||
if(asmParamReg.statusflag!=null) {
|
||||
if(argDt !in ByteDatatypes)
|
||||
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for statusflag", position)
|
||||
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
if(argDt !in ByteDatatypes)
|
||||
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for single register", position)
|
||||
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
|
||||
if(argDt !in WordDatatypes + IterableDatatypes)
|
||||
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -969,7 +975,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(target is VarDecl) {
|
||||
if(target.datatype !in IterableDatatypes)
|
||||
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
|
||||
val arraysize = target.arraysize?.size()
|
||||
val arraysize = target.arraysize?.constIndex()
|
||||
if(arraysize!=null) {
|
||||
// check out of bounds
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
|
||||
@ -1050,35 +1056,14 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(scope: AnonymousScope) {
|
||||
visitStatements(scope.statements)
|
||||
}
|
||||
|
||||
private fun visitStatements(statements: List<Statement>) {
|
||||
for((index, stmt) in statements.withIndex()) {
|
||||
if(index < statements.lastIndex && statements[index+1] !is Subroutine) {
|
||||
when {
|
||||
stmt is FunctionCallStatement && stmt.target.nameInSource.last() == "exit" -> {
|
||||
errors.warn("unreachable code, preceding exit call will never return", statements[index + 1].position)
|
||||
}
|
||||
stmt is Return && statements[index + 1] !is Subroutine -> {
|
||||
errors.warn("unreachable code, preceding return statement", statements[index + 1].position)
|
||||
}
|
||||
stmt is Jump && statements[index + 1] !is Subroutine -> {
|
||||
errors.warn("unreachable code, preceding jump statement", statements[index + 1].position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stmt.accept(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
|
||||
val targetStatement = target.targetStatement(program.namespace)
|
||||
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
return targetStatement
|
||||
errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
else if(targetStatement==null)
|
||||
errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
else
|
||||
errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position)
|
||||
return null
|
||||
}
|
||||
|
||||
@ -1111,7 +1096,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(value.type.istype(targetDt)) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySpecSize = arrayspec.size()
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
val arraySize = value.value.size
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>256)
|
||||
@ -1133,7 +1118,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(value.type.istype(targetDt)) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySpecSize = arrayspec.size()
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
val arraySize = value.value.size
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>128)
|
||||
@ -1156,7 +1141,7 @@ internal class AstChecker(private val program: Program,
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
val arraySize = value.value.size
|
||||
val arraySpecSize = arrayspec.size()
|
||||
val arraySpecSize = arrayspec.constIndex()
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize < 1 || arraySpecSize>51)
|
||||
return err("float array length must be 1-51")
|
||||
@ -1248,7 +1233,11 @@ internal class AstChecker(private val program: Program,
|
||||
is AddressOf -> it.identifier.heapId(program.namespace)
|
||||
is TypecastExpression -> {
|
||||
val constVal = it.expression.constValue(program)
|
||||
constVal?.cast(it.type)?.number?.toInt() ?: -9999999
|
||||
val cast = constVal?.cast(it.type)
|
||||
if(cast==null || !cast.isValid)
|
||||
-9999999
|
||||
else
|
||||
cast.valueOrZero().number.toInt()
|
||||
}
|
||||
else -> -9999999
|
||||
}
|
||||
@ -1293,8 +1282,8 @@ internal class AstChecker(private val program: Program,
|
||||
DataType.STR -> sourceDatatype== DataType.STR
|
||||
DataType.STRUCT -> {
|
||||
if(sourceDatatype==DataType.STRUCT) {
|
||||
val structLv = sourceValue as StructLiteralValue
|
||||
val numValues = structLv.values.size
|
||||
val structLv = sourceValue as ArrayLiteralValue
|
||||
val numValues = structLv.value.size
|
||||
val targetstruct = target.identifier!!.targetVarDecl(program.namespace)!!.struct!!
|
||||
return targetstruct.numberOfElements == numValues
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
@ -10,98 +8,72 @@ import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
internal class AstIdentifiersChecker(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
|
||||
private var blocks = mutableMapOf<String, Block>()
|
||||
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||
|
||||
private fun nameError(name: String, position: Position, existing: Statement) {
|
||||
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
|
||||
}
|
||||
|
||||
override fun visit(module: Module) {
|
||||
vardeclsToAdd.clear()
|
||||
blocks.clear() // blocks may be redefined within a different module
|
||||
|
||||
super.visit(module)
|
||||
// add any new vardecls to the various scopes
|
||||
for((where, decls) in vardeclsToAdd) {
|
||||
where.statements.addAll(0, decls)
|
||||
decls.forEach { it.linkParents(where as Node) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(block: Block): Statement {
|
||||
override fun visit(block: Block) {
|
||||
if(block.name in CompilationTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
|
||||
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null)
|
||||
nameError(block.name, block.position, existing)
|
||||
else
|
||||
blocks[block.name] = block
|
||||
|
||||
return super.visit(block)
|
||||
super.visit(block)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
|
||||
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
|
||||
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
|
||||
typecast.linkParents(functionCall.parent)
|
||||
return super.visit(typecast)
|
||||
}
|
||||
return super.visit(functionCall)
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
// first, check if there are datatype errors on the vardecl
|
||||
override fun visit(decl: VarDecl) {
|
||||
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
|
||||
|
||||
// now check the identifier
|
||||
if(decl.name in BuiltinFunctions)
|
||||
// the builtin functions can't be redefined
|
||||
errors.err("builtin function cannot be redefined", decl.position)
|
||||
|
||||
if(decl.name in CompilationTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
|
||||
|
||||
// is it a struct variable? then define all its struct members as mangled names,
|
||||
// and include the original decl as well.
|
||||
if(decl.datatype==DataType.STRUCT) {
|
||||
if(decl.structHasBeenFlattened)
|
||||
if (decl.structHasBeenFlattened)
|
||||
return super.visit(decl) // don't do this multiple times
|
||||
|
||||
if(decl.struct==null) {
|
||||
if (decl.struct == null) {
|
||||
errors.err("undefined struct type", decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
|
||||
if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes })
|
||||
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
|
||||
|
||||
if(decl.value is NumericLiteralValue) {
|
||||
if (decl.value is NumericLiteralValue) {
|
||||
errors.err("you cannot initialize a struct using a single value", decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
if(decl.value != null && decl.value !is StructLiteralValue) {
|
||||
errors.err("initializing requires struct literal value", decl.value?.position ?: decl.position)
|
||||
if (decl.value != null && decl.value !is ArrayLiteralValue) {
|
||||
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
|
||||
return super.visit(decl)
|
||||
}
|
||||
|
||||
val decls = decl.flattenStructMembers()
|
||||
decls.add(decl)
|
||||
val result = AnonymousScope(decls, decl.position)
|
||||
result.linkParents(decl.parent)
|
||||
return result
|
||||
}
|
||||
|
||||
val existing = program.namespace.lookup(listOf(decl.name), decl)
|
||||
if (existing != null && existing !== decl)
|
||||
nameError(decl.name, decl.position, existing)
|
||||
|
||||
return super.visit(decl)
|
||||
super.visit(decl)
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
|
||||
} else if(subroutine.name in BuiltinFunctions) {
|
||||
@ -138,30 +110,15 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
nameError(name, sub.position, subroutine)
|
||||
}
|
||||
|
||||
// inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters)
|
||||
// NOTE:
|
||||
// - numeric types BYTE and WORD and FLOAT are passed by value;
|
||||
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(subroutine.asmParameterRegisters.isEmpty()) {
|
||||
subroutine.parameters
|
||||
.filter { it.name !in namesInSub }
|
||||
.forEach {
|
||||
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||
vardecl.linkParents(subroutine)
|
||||
subroutine.statements.add(0, vardecl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
|
||||
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
|
||||
}
|
||||
}
|
||||
return super.visit(subroutine)
|
||||
|
||||
super.visit(subroutine)
|
||||
}
|
||||
|
||||
override fun visit(label: Label): Statement {
|
||||
override fun visit(label: Label) {
|
||||
if(label.name in CompilationTarget.machine.opcodeNames)
|
||||
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
|
||||
|
||||
@ -179,163 +136,24 @@ internal class AstIdentifiersChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(label)
|
||||
|
||||
super.visit(label)
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop): Statement {
|
||||
// If the for loop has a decltype, it means to declare the loopvar inside the loop body
|
||||
// rather than reusing an already declared loopvar from an outer scope.
|
||||
// For loops that loop over an interable variable (instead of a range of numbers) get an
|
||||
// additional interation count variable in their scope.
|
||||
if(forLoop.loopRegister!=null) {
|
||||
if(forLoop.loopRegister == Register.X)
|
||||
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
||||
} else {
|
||||
val loopVar = forLoop.loopVar
|
||||
if (loopVar != null) {
|
||||
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
|
||||
val loopvarName = "prog8_loopvar_$validName"
|
||||
if (forLoop.iterable !is RangeExpr) {
|
||||
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first())
|
||||
if (existing == null) {
|
||||
// create loop iteration counter variable (without value, to avoid an assignment)
|
||||
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null,
|
||||
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
|
||||
vardecl.linkParents(forLoop.body)
|
||||
forLoop.body.statements.add(0, vardecl)
|
||||
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.visit(forLoop)
|
||||
override fun visit(string: StringLiteralValue) {
|
||||
if (string.value.length !in 1..255)
|
||||
errors.err("string literal length must be between 1 and 255", string.position)
|
||||
|
||||
super.visit(string)
|
||||
}
|
||||
|
||||
override fun visit(assignTarget: AssignTarget): AssignTarget {
|
||||
if(assignTarget.register== Register.X)
|
||||
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
|
||||
return super.visit(assignTarget)
|
||||
}
|
||||
|
||||
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||
val array = super.visit(arrayLiteral)
|
||||
if(array is ArrayLiteralValue) {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
if(vardecl!=null) {
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null) {
|
||||
vardecl.value = cast
|
||||
cast.linkParents(vardecl)
|
||||
return cast
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
return if (litval2 != null) {
|
||||
litval2.parent = array.parent
|
||||
makeIdentifierFromRefLv(litval2)
|
||||
} else array
|
||||
}
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
override fun visit(stringLiteral: StringLiteralValue): Expression {
|
||||
val string = super.visit(stringLiteral)
|
||||
if(string is StringLiteralValue) {
|
||||
val vardecl = string.parent as? VarDecl
|
||||
// intern the string; move it into the heap
|
||||
if (string.value.length !in 1..255)
|
||||
errors.err("string literal length must be between 1 and 255", string.position)
|
||||
return if (vardecl != null)
|
||||
string
|
||||
else
|
||||
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
|
||||
// a referencetype literal value that's not declared as a variable
|
||||
// we need to introduce an auto-generated variable for this to be able to refer to the value
|
||||
// note: if the var references the same literal value, it is not yet de-duplicated here.
|
||||
val scope = array.definingScope()
|
||||
val variable = VarDecl.createAuto(array)
|
||||
return replaceWithIdentifier(variable, scope, array.parent)
|
||||
}
|
||||
|
||||
private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
|
||||
// a referencetype literal value that's not declared as a variable
|
||||
// we need to introduce an auto-generated variable for this to be able to refer to the value
|
||||
// note: if the var references the same literal value, it is not yet de-duplicated here.
|
||||
val scope = string.definingScope()
|
||||
val variable = VarDecl.createAuto(string)
|
||||
return replaceWithIdentifier(variable, scope, string.parent)
|
||||
}
|
||||
|
||||
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
|
||||
val variable1 = addVarDecl(scope, variable)
|
||||
// replace the reference literal by a identifier reference
|
||||
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
|
||||
identifier.parent = parent
|
||||
return identifier
|
||||
}
|
||||
|
||||
override fun visit(structDecl: StructDecl): Statement {
|
||||
override fun visit(structDecl: StructDecl) {
|
||||
for(member in structDecl.statements){
|
||||
val decl = member as? VarDecl
|
||||
if(decl!=null && decl.datatype !in NumericDatatypes)
|
||||
errors.err("structs can only contain numerical types", decl.position)
|
||||
}
|
||||
|
||||
return super.visit(structDecl)
|
||||
super.visit(structDecl)
|
||||
}
|
||||
|
||||
override fun visit(expr: BinaryExpression): Expression {
|
||||
return when {
|
||||
expr.left is StringLiteralValue ->
|
||||
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
|
||||
expr.right is StringLiteralValue ->
|
||||
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
|
||||
else -> super.visit(expr)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
||||
val constvalue = operand.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if (expr.operator == "*") {
|
||||
// repeat a string a number of times
|
||||
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
|
||||
}
|
||||
}
|
||||
if(expr.operator == "+" && operand is StringLiteralValue) {
|
||||
// concatenate two strings
|
||||
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
|
||||
if(scope !in vardeclsToAdd)
|
||||
vardeclsToAdd[scope] = mutableListOf()
|
||||
val declList = vardeclsToAdd.getValue(scope)
|
||||
val existing = declList.singleOrNull { it.name==variable.name }
|
||||
return if(existing!=null) {
|
||||
existing
|
||||
} else {
|
||||
declList.add(variable)
|
||||
variable
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
161
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
161
compiler/src/prog8/ast/processing/AstVariousTransforms.kt
Normal file
@ -0,0 +1,161 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource == listOf("swap")) {
|
||||
// if x and y are both just identifiers, do not rewrite (there should be asm generation for that)
|
||||
// otherwise:
|
||||
// rewrite swap(x,y) as follows:
|
||||
// - declare a temp variable of the same datatype
|
||||
// - temp = x, x = y, y= temp
|
||||
val first = functionCallStatement.args[0]
|
||||
val second = functionCallStatement.args[1]
|
||||
if(first !is IdentifierReference && second !is IdentifierReference) {
|
||||
val dt = first.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}"
|
||||
val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position)
|
||||
val tempvar = IdentifierReference(listOf(tempname), first.position)
|
||||
val assignTemp = Assignment(
|
||||
AssignTarget(tempvar, null, null, first.position),
|
||||
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.
|
||||
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
|
||||
val decls = decl.flattenStructMembers()
|
||||
decls.add(decl)
|
||||
val result = AnonymousScope(decls, decl.position)
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
decl, result, parent
|
||||
))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// For non-kernel subroutines and non-asm parameters:
|
||||
// inject subroutine params as local variables (if they're not there yet).
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
|
||||
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
|
||||
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
|
||||
return subroutine.parameters
|
||||
.filter { it.name !in namesInSub }
|
||||
.map {
|
||||
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
|
||||
IAstModification.InsertFirst(vardecl, subroutine)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
when {
|
||||
expr.left is StringLiteralValue ->
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
|
||||
parent
|
||||
))
|
||||
expr.right is StringLiteralValue ->
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
|
||||
parent
|
||||
))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
if(string.parent !is VarDecl) {
|
||||
// replace the literal string by a identifier reference to a new local vardecl
|
||||
val vardecl = VarDecl.createAuto(string)
|
||||
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(string, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
|
||||
)
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
val vardecl = array.parent as? VarDecl
|
||||
if(vardecl!=null) {
|
||||
// adjust the datatype of the array (to an educated guess)
|
||||
val arrayDt = array.type
|
||||
if(!arrayDt.istype(vardecl.datatype)) {
|
||||
val cast = array.cast(vardecl.datatype)
|
||||
if (cast != null && cast!=array)
|
||||
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
|
||||
}
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
// this array literal is part of an expression, turn it into an identifier reference
|
||||
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if(litval2!=null && litval2!=array) {
|
||||
val vardecl2 = VarDecl.createAuto(litval2)
|
||||
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(array, identifier, parent),
|
||||
IAstModification.InsertFirst(vardecl2, array.definingScope() as Node)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
|
||||
val constvalue = operand.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if (expr.operator == "*") {
|
||||
// repeat a string a number of times
|
||||
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
|
||||
}
|
||||
}
|
||||
if(expr.operator == "+" && operand is StringLiteralValue) {
|
||||
// concatenate two strings
|
||||
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
@ -15,7 +12,7 @@ interface IAstModification {
|
||||
class Remove(val node: Node, val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
if (!parent.statements.remove(node))
|
||||
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
|
||||
throw FatalAstException("attempt to remove non-existing node $node")
|
||||
} else {
|
||||
throw FatalAstException("parent of a remove modification is not an INameScope")
|
||||
@ -41,10 +38,33 @@ interface IAstModification {
|
||||
}
|
||||
}
|
||||
|
||||
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
parent.statements.add(stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
val idx = parent.statements.indexOf(after)+1
|
||||
val idx = parent.statements.indexOfFirst { it===after } + 1
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
throw FatalAstException("parent of an insert modification is not an INameScope")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InsertBefore(val before: Statement, val stmt: Statement, val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
if(parent is INameScope) {
|
||||
val idx = parent.statements.indexOfFirst { it===before }
|
||||
parent.statements.add(idx, stmt)
|
||||
stmt.linkParents(parent)
|
||||
} else {
|
||||
@ -56,7 +76,7 @@ interface IAstModification {
|
||||
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
|
||||
override fun perform() {
|
||||
parent.replaceChildNode(node, replacement)
|
||||
replacement.parent = parent
|
||||
replacement.linkParents(parent)
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,13 +100,12 @@ abstract class AstWalker {
|
||||
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(foreverLoop: ForeverLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -102,13 +121,11 @@ abstract class AstWalker {
|
||||
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -124,13 +141,12 @@ abstract class AstWalker {
|
||||
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(foreverLoop: ForeverLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -146,13 +162,11 @@ abstract class AstWalker {
|
||||
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
|
||||
@ -305,11 +319,6 @@ abstract class AstWalker {
|
||||
track(after(postIncrDecr, parent), postIncrDecr, parent)
|
||||
}
|
||||
|
||||
fun visit(contStmt: Continue, parent: Node) {
|
||||
track(before(contStmt, parent), contStmt, parent)
|
||||
track(after(contStmt, parent), contStmt, parent)
|
||||
}
|
||||
|
||||
fun visit(breakStmt: Break, parent: Node) {
|
||||
track(before(breakStmt, parent), breakStmt, parent)
|
||||
track(after(breakStmt, parent), breakStmt, parent)
|
||||
@ -317,7 +326,7 @@ abstract class AstWalker {
|
||||
|
||||
fun visit(forLoop: ForLoop, parent: Node) {
|
||||
track(before(forLoop, parent), forLoop, parent)
|
||||
forLoop.loopVar?.accept(this, forLoop)
|
||||
forLoop.loopVar.accept(this, forLoop)
|
||||
forLoop.iterable.accept(this, forLoop)
|
||||
forLoop.body.accept(this, forLoop)
|
||||
track(after(forLoop, parent), forLoop, parent)
|
||||
@ -330,19 +339,20 @@ abstract class AstWalker {
|
||||
track(after(whileLoop, parent), whileLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(foreverLoop: ForeverLoop, parent: Node) {
|
||||
track(before(foreverLoop, parent), foreverLoop, parent)
|
||||
foreverLoop.body.accept(this, foreverLoop)
|
||||
track(after(foreverLoop, parent), foreverLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop, parent: Node) {
|
||||
track(before(repeatLoop, parent), repeatLoop, parent)
|
||||
repeatLoop.untilCondition.accept(this, repeatLoop)
|
||||
repeatLoop.iterations?.accept(this, repeatLoop)
|
||||
repeatLoop.body.accept(this, repeatLoop)
|
||||
track(after(repeatLoop, parent), repeatLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(untilLoop: UntilLoop, parent: Node) {
|
||||
track(before(untilLoop, parent), untilLoop, parent)
|
||||
untilLoop.untilCondition.accept(this, untilLoop)
|
||||
untilLoop.body.accept(this, untilLoop)
|
||||
track(after(untilLoop, parent), untilLoop, parent)
|
||||
}
|
||||
|
||||
fun visit(returnStmt: Return, parent: Node) {
|
||||
track(before(returnStmt, parent), returnStmt, parent)
|
||||
returnStmt.value?.accept(this, returnStmt)
|
||||
@ -399,11 +409,6 @@ abstract class AstWalker {
|
||||
track(after(inlineAssembly, parent), inlineAssembly, parent)
|
||||
}
|
||||
|
||||
fun visit(registerExpr: RegisterExpr, parent: Node) {
|
||||
track(before(registerExpr, parent), registerExpr, parent)
|
||||
track(after(registerExpr, parent), registerExpr, parent)
|
||||
}
|
||||
|
||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) {
|
||||
track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
|
||||
track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
|
||||
@ -433,11 +438,5 @@ abstract class AstWalker {
|
||||
structDecl.statements.forEach { it.accept(this, structDecl) }
|
||||
track(after(structDecl, parent), structDecl, parent)
|
||||
}
|
||||
|
||||
fun visit(structLv: StructLiteralValue, parent: Node) {
|
||||
track(before(structLv, parent), structLv, parent)
|
||||
structLv.values.forEach { it.accept(this, structLv) }
|
||||
track(after(structLv, parent), structLv, parent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.statements.ForeverLoop
|
||||
import prog8.ast.statements.RepeatLoop
|
||||
import prog8.ast.statements.WhileLoop
|
||||
|
||||
|
||||
internal class ForeverLoopsMaker: AstWalker() {
|
||||
override fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
val numeric = repeatLoop.untilCondition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() == 0) {
|
||||
val forever = ForeverLoop(repeatLoop.body, repeatLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(repeatLoop, forever, parent))
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val numeric = whileLoop.condition as? NumericLiteralValue
|
||||
if(numeric!=null && numeric.number.toInt() != 0) {
|
||||
val forever = ForeverLoop(whileLoop.body, whileLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
}
|
@ -1,267 +0,0 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
interface IAstModifyingVisitor {
|
||||
fun visit(program: Program) {
|
||||
program.modules.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(module: Module) {
|
||||
module.statements = module.statements.map { it.accept(this) }.toMutableList()
|
||||
}
|
||||
|
||||
fun visit(expr: PrefixExpression): Expression {
|
||||
expr.expression = expr.expression.accept(this)
|
||||
return expr
|
||||
}
|
||||
|
||||
fun visit(expr: BinaryExpression): Expression {
|
||||
expr.left = expr.left.accept(this)
|
||||
expr.right = expr.right.accept(this)
|
||||
return expr
|
||||
}
|
||||
|
||||
fun visit(directive: Directive): Statement {
|
||||
return directive
|
||||
}
|
||||
|
||||
fun visit(block: Block): Statement {
|
||||
block.statements = block.statements.map { it.accept(this) }.toMutableList()
|
||||
return block
|
||||
}
|
||||
|
||||
fun visit(decl: VarDecl): Statement {
|
||||
decl.value = decl.value?.accept(this)
|
||||
decl.arraysize?.accept(this)
|
||||
return decl
|
||||
}
|
||||
|
||||
fun visit(subroutine: Subroutine): Statement {
|
||||
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
|
||||
return subroutine
|
||||
}
|
||||
|
||||
fun visit(functionCall: FunctionCall): Expression {
|
||||
val newtarget = functionCall.target.accept(this)
|
||||
if(newtarget is IdentifierReference)
|
||||
functionCall.target = newtarget
|
||||
else
|
||||
throw FatalAstException("cannot change class of function call target")
|
||||
functionCall.args = functionCall.args.map { it.accept(this) }.toMutableList()
|
||||
return functionCall
|
||||
}
|
||||
|
||||
fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
val newtarget = functionCallStatement.target.accept(this)
|
||||
if(newtarget is IdentifierReference)
|
||||
functionCallStatement.target = newtarget
|
||||
else
|
||||
throw FatalAstException("cannot change class of function call target")
|
||||
functionCallStatement.args = functionCallStatement.args.map { it.accept(this) }.toMutableList()
|
||||
return functionCallStatement
|
||||
}
|
||||
|
||||
fun visit(identifier: IdentifierReference): Expression {
|
||||
// note: this is an identifier that is used in an expression.
|
||||
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
|
||||
return identifier
|
||||
}
|
||||
|
||||
fun visit(jump: Jump): Statement {
|
||||
if(jump.identifier!=null) {
|
||||
val ident = jump.identifier.accept(this)
|
||||
if(ident is IdentifierReference && ident!==jump.identifier) {
|
||||
return Jump(null, ident, null, jump.position)
|
||||
}
|
||||
}
|
||||
return jump
|
||||
}
|
||||
|
||||
fun visit(ifStatement: IfStatement): Statement {
|
||||
ifStatement.condition = ifStatement.condition.accept(this)
|
||||
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
|
||||
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
|
||||
return ifStatement
|
||||
}
|
||||
|
||||
fun visit(branchStatement: BranchStatement): Statement {
|
||||
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
|
||||
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
|
||||
return branchStatement
|
||||
}
|
||||
|
||||
fun visit(range: RangeExpr): Expression {
|
||||
range.from = range.from.accept(this)
|
||||
range.to = range.to.accept(this)
|
||||
range.step = range.step.accept(this)
|
||||
return range
|
||||
}
|
||||
|
||||
fun visit(label: Label): Statement {
|
||||
return label
|
||||
}
|
||||
|
||||
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
|
||||
return literalValue
|
||||
}
|
||||
|
||||
fun visit(stringLiteral: StringLiteralValue): Expression {
|
||||
return stringLiteral
|
||||
}
|
||||
|
||||
fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||
for(av in arrayLiteral.value.withIndex()) {
|
||||
val newvalue = av.value.accept(this)
|
||||
arrayLiteral.value[av.index] = newvalue
|
||||
}
|
||||
return arrayLiteral
|
||||
}
|
||||
|
||||
fun visit(assignment: Assignment): Statement {
|
||||
assignment.target = assignment.target.accept(this)
|
||||
assignment.value = assignment.value.accept(this)
|
||||
return assignment
|
||||
}
|
||||
|
||||
fun visit(postIncrDecr: PostIncrDecr): Statement {
|
||||
postIncrDecr.target = postIncrDecr.target.accept(this)
|
||||
return postIncrDecr
|
||||
}
|
||||
|
||||
fun visit(contStmt: Continue): Statement {
|
||||
return contStmt
|
||||
}
|
||||
|
||||
fun visit(breakStmt: Break): Statement {
|
||||
return breakStmt
|
||||
}
|
||||
|
||||
fun visit(forLoop: ForLoop): Statement {
|
||||
when(val newloopvar = forLoop.loopVar?.accept(this)) {
|
||||
is IdentifierReference -> forLoop.loopVar = newloopvar
|
||||
null -> forLoop.loopVar = null
|
||||
else -> throw FatalAstException("can't change class of loopvar")
|
||||
}
|
||||
forLoop.iterable = forLoop.iterable.accept(this)
|
||||
forLoop.body = forLoop.body.accept(this) as AnonymousScope
|
||||
return forLoop
|
||||
}
|
||||
|
||||
fun visit(whileLoop: WhileLoop): Statement {
|
||||
whileLoop.condition = whileLoop.condition.accept(this)
|
||||
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
|
||||
return whileLoop
|
||||
}
|
||||
|
||||
fun visit(foreverLoop: ForeverLoop): Statement {
|
||||
foreverLoop.body = foreverLoop.body.accept(this) as AnonymousScope
|
||||
return foreverLoop
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop): Statement {
|
||||
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
|
||||
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
|
||||
return repeatLoop
|
||||
}
|
||||
|
||||
fun visit(returnStmt: Return): Statement {
|
||||
returnStmt.value = returnStmt.value?.accept(this)
|
||||
return returnStmt
|
||||
}
|
||||
|
||||
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
|
||||
val ident = arrayIndexedExpression.identifier.accept(this)
|
||||
if(ident is IdentifierReference)
|
||||
arrayIndexedExpression.identifier = ident
|
||||
arrayIndexedExpression.arrayspec.accept(this)
|
||||
return arrayIndexedExpression
|
||||
}
|
||||
|
||||
fun visit(assignTarget: AssignTarget): AssignTarget {
|
||||
when (val ident = assignTarget.identifier?.accept(this)) {
|
||||
is IdentifierReference -> assignTarget.identifier = ident
|
||||
null -> assignTarget.identifier = null
|
||||
else -> throw FatalAstException("can't change class of assign target identifier")
|
||||
}
|
||||
assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this)
|
||||
assignTarget.memoryAddress?.let { visit(it) }
|
||||
return assignTarget
|
||||
}
|
||||
|
||||
fun visit(scope: AnonymousScope): Statement {
|
||||
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
|
||||
return scope
|
||||
}
|
||||
|
||||
fun visit(typecast: TypecastExpression): Expression {
|
||||
typecast.expression = typecast.expression.accept(this)
|
||||
return typecast
|
||||
}
|
||||
|
||||
fun visit(memread: DirectMemoryRead): Expression {
|
||||
memread.addressExpression = memread.addressExpression.accept(this)
|
||||
return memread
|
||||
}
|
||||
|
||||
fun visit(memwrite: DirectMemoryWrite) {
|
||||
memwrite.addressExpression = memwrite.addressExpression.accept(this)
|
||||
}
|
||||
|
||||
fun visit(addressOf: AddressOf): Expression {
|
||||
val ident = addressOf.identifier.accept(this)
|
||||
if(ident is IdentifierReference)
|
||||
addressOf.identifier = ident
|
||||
else
|
||||
throw FatalAstException("can't change class of addressof identifier")
|
||||
return addressOf
|
||||
}
|
||||
|
||||
fun visit(inlineAssembly: InlineAssembly): Statement {
|
||||
return inlineAssembly
|
||||
}
|
||||
|
||||
fun visit(registerExpr: RegisterExpr): Expression {
|
||||
return registerExpr
|
||||
}
|
||||
|
||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
|
||||
return builtinFunctionStatementPlaceholder
|
||||
}
|
||||
|
||||
fun visit(nopStatement: NopStatement): Statement {
|
||||
return nopStatement
|
||||
}
|
||||
|
||||
fun visit(whenStatement: WhenStatement): Statement {
|
||||
whenStatement.condition = whenStatement.condition.accept(this)
|
||||
whenStatement.choices.forEach { it.accept(this) }
|
||||
return whenStatement
|
||||
}
|
||||
|
||||
fun visit(whenChoice: WhenChoice) {
|
||||
whenChoice.values = whenChoice.values?.map { it.accept(this) }
|
||||
val stmt = whenChoice.statements.accept(this)
|
||||
if(stmt is AnonymousScope)
|
||||
whenChoice.statements = stmt
|
||||
else {
|
||||
whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position)
|
||||
whenChoice.statements.linkParents(whenChoice)
|
||||
}
|
||||
}
|
||||
|
||||
fun visit(structDecl: StructDecl): Statement {
|
||||
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
|
||||
return structDecl
|
||||
}
|
||||
|
||||
fun visit(structLv: StructLiteralValue): Expression {
|
||||
structLv.values = structLv.values.map { it.accept(this) }
|
||||
return structLv
|
||||
}
|
||||
}
|
@ -95,14 +95,11 @@ interface IAstVisitor {
|
||||
postIncrDecr.target.accept(this)
|
||||
}
|
||||
|
||||
fun visit(contStmt: Continue) {
|
||||
}
|
||||
|
||||
fun visit(breakStmt: Break) {
|
||||
}
|
||||
|
||||
fun visit(forLoop: ForLoop) {
|
||||
forLoop.loopVar?.accept(this)
|
||||
forLoop.loopVar.accept(this)
|
||||
forLoop.iterable.accept(this)
|
||||
forLoop.body.accept(this)
|
||||
}
|
||||
@ -112,13 +109,14 @@ interface IAstVisitor {
|
||||
whileLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(foreverLoop: ForeverLoop) {
|
||||
foreverLoop.body.accept(this)
|
||||
fun visit(repeatLoop: RepeatLoop) {
|
||||
repeatLoop.iterations?.accept(this)
|
||||
repeatLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(repeatLoop: RepeatLoop) {
|
||||
repeatLoop.untilCondition.accept(this)
|
||||
repeatLoop.body.accept(this)
|
||||
fun visit(untilLoop: UntilLoop) {
|
||||
untilLoop.untilCondition.accept(this)
|
||||
untilLoop.body.accept(this)
|
||||
}
|
||||
|
||||
fun visit(returnStmt: Return) {
|
||||
@ -159,9 +157,6 @@ interface IAstVisitor {
|
||||
fun visit(inlineAssembly: InlineAssembly) {
|
||||
}
|
||||
|
||||
fun visit(registerExpr: RegisterExpr) {
|
||||
}
|
||||
|
||||
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
|
||||
}
|
||||
|
||||
@ -181,8 +176,4 @@ interface IAstVisitor {
|
||||
fun visit(structDecl: StructDecl) {
|
||||
structDecl.statements.forEach { it.accept(this) }
|
||||
}
|
||||
|
||||
fun visit(structLv: StructLiteralValue) {
|
||||
structLv.values.forEach { it.accept(this) }
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,12 @@ internal class ImportedModuleDirectiveRemover: AstWalker() {
|
||||
*/
|
||||
|
||||
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
|
||||
if(directive.directive in moduleLevelDirectives) {
|
||||
return listOf(IAstModification.Remove(directive, parent))
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -1,195 +1,170 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
|
||||
internal class StatementReorderer(val program: Program) : AstWalker() {
|
||||
// Reorders the statements in a way the compiler needs.
|
||||
// - 'main' block must be the very first statement UNLESS it has an address set.
|
||||
// - blocks are ordered by address, where blocks without address are put at the end.
|
||||
// - in every scope:
|
||||
// -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first.
|
||||
// -- all vardecls then follow.
|
||||
// -- the remaining statements then follow in their original order.
|
||||
//
|
||||
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
|
||||
// - all other subroutines will be moved to the end of their block.
|
||||
// - library blocks are put last.
|
||||
// - blocks are ordered by address, where blocks without address are placed last.
|
||||
// - in every scope, most directives and vardecls are moved to the top.
|
||||
// - the 'start' subroutine is moved to the top.
|
||||
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
|
||||
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
|
||||
// - sorts the choices in when statement.
|
||||
// - a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
|
||||
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
|
||||
|
||||
private val addVardecls = mutableMapOf<INameScope, MutableList<VarDecl>>()
|
||||
|
||||
override fun visit(module: Module) {
|
||||
addVardecls.clear()
|
||||
super.visit(module)
|
||||
|
||||
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
|
||||
val (blocks, other) = module.statements.partition { it is Block }
|
||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
||||
|
||||
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything
|
||||
val nonLibraryBlocks = module.statements.withIndex()
|
||||
.filter { it.value is Block && !(it.value as Block).isInLibrary }
|
||||
.map { it.index to it.value }
|
||||
.reversed()
|
||||
for(nonLibBlock in nonLibraryBlocks)
|
||||
module.statements.removeAt(nonLibBlock.first)
|
||||
for(nonLibBlock in nonLibraryBlocks)
|
||||
module.statements.add(0, nonLibBlock.second)
|
||||
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
|
||||
if(mainBlock!=null && (mainBlock as Block).address==null) {
|
||||
module.remove(mainBlock)
|
||||
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
|
||||
if(mainBlock!=null && mainBlock.address==null) {
|
||||
module.statements.remove(mainBlock)
|
||||
module.statements.add(0, mainBlock)
|
||||
}
|
||||
|
||||
val varDecls = module.statements.filterIsInstance<VarDecl>()
|
||||
module.statements.removeAll(varDecls)
|
||||
module.statements.addAll(0, varDecls)
|
||||
|
||||
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
|
||||
module.statements.removeAll(directives)
|
||||
module.statements.addAll(0, directives)
|
||||
|
||||
for((where, decls) in addVardecls) {
|
||||
where.statements.addAll(0, decls)
|
||||
decls.forEach { it.linkParents(where as Node) }
|
||||
}
|
||||
reorderVardeclsAndDirectives(module.statements)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(block: Block): Statement {
|
||||
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
|
||||
val varDecls = statements.filterIsInstance<VarDecl>()
|
||||
statements.removeAll(varDecls)
|
||||
statements.addAll(0, varDecls)
|
||||
|
||||
val subroutines = block.statements.filterIsInstance<Subroutine>()
|
||||
var numSubroutinesAtEnd = 0
|
||||
// move all subroutines to the end of the block
|
||||
for (subroutine in subroutines) {
|
||||
if(subroutine.name!="start" || block.name!="main") {
|
||||
block.remove(subroutine)
|
||||
block.statements.add(subroutine)
|
||||
}
|
||||
numSubroutinesAtEnd++
|
||||
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
|
||||
statements.removeAll(directives)
|
||||
statements.addAll(0, directives)
|
||||
}
|
||||
|
||||
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
parent as Module
|
||||
if(block.isInLibrary) {
|
||||
return listOf(
|
||||
IAstModification.Remove(block, parent),
|
||||
IAstModification.InsertLast(block, parent)
|
||||
)
|
||||
}
|
||||
// move the "start" subroutine to the top
|
||||
if(block.name=="main") {
|
||||
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
|
||||
block.remove(it)
|
||||
block.statements.add(0, it)
|
||||
numSubroutinesAtEnd--
|
||||
|
||||
reorderVardeclsAndDirectives(block.statements)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
if(subroutine.name=="start" && parent is Block) {
|
||||
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
|
||||
return listOf(
|
||||
IAstModification.Remove(subroutine, parent),
|
||||
IAstModification.InsertFirst(subroutine, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val varDecls = block.statements.filterIsInstance<VarDecl>()
|
||||
block.statements.removeAll(varDecls)
|
||||
block.statements.addAll(0, varDecls)
|
||||
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
|
||||
block.statements.removeAll(directives)
|
||||
block.statements.addAll(0, directives)
|
||||
block.linkParents(block.parent)
|
||||
|
||||
return super.visit(block)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
super.visit(subroutine)
|
||||
|
||||
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
|
||||
subroutine.statements.removeAll(varDecls)
|
||||
subroutine.statements.addAll(0, varDecls)
|
||||
val directives = subroutine.statements.filter {it is Directive && it.directive in directivesToMove}
|
||||
subroutine.statements.removeAll(directives)
|
||||
subroutine.statements.addAll(0, directives)
|
||||
|
||||
return subroutine
|
||||
}
|
||||
|
||||
|
||||
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
|
||||
if(scope !in addVardecls)
|
||||
addVardecls[scope] = mutableListOf()
|
||||
val declList = addVardecls.getValue(scope)
|
||||
val existing = declList.singleOrNull { it.name==variable.name }
|
||||
return if(existing!=null) {
|
||||
existing
|
||||
} else {
|
||||
declList.add(variable)
|
||||
variable
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val declValue = decl.value
|
||||
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
val declConstValue = declValue.constValue(program)
|
||||
if(declConstValue==null) {
|
||||
// move the vardecl (without value) to the scope and replace this with a regular assignment
|
||||
val target = AssignTarget(null, IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, null, declValue, decl.position)
|
||||
assign.linkParents(decl.parent)
|
||||
decl.value = null
|
||||
addVarDecl(decl.definingScope(), decl)
|
||||
return assign
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, declValue, decl.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, assign, parent),
|
||||
IAstModification.InsertFirst(decl, decl.definingScope() as Node)
|
||||
)
|
||||
}
|
||||
}
|
||||
return super.visit(decl)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
val assg = super.visit(assignment)
|
||||
if(assg !is Assignment)
|
||||
return assg
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
val choices = whenStatement.choiceValues(program).sortedBy {
|
||||
it.first?.first() ?: Int.MAX_VALUE
|
||||
}
|
||||
whenStatement.choices.clear()
|
||||
choices.mapTo(whenStatement.choices) { it.second }
|
||||
return noModifications
|
||||
}
|
||||
|
||||
// see if a typecast is needed to convert the value's type into the proper target type
|
||||
val valueItype = assg.value.inferType(program)
|
||||
val targetItype = assg.target.inferType(program, assg)
|
||||
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)) {
|
||||
val assignments = if (assignment.value is ArrayLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
|
||||
} else {
|
||||
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
|
||||
}
|
||||
if(assignments.isNotEmpty()) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
|
||||
modifications.add(IAstModification.Remove(assignment, parent))
|
||||
return modifications
|
||||
}
|
||||
}
|
||||
|
||||
if(targetItype.isKnown && valueItype.isKnown) {
|
||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
// struct assignments will be flattened (if it's not a struct literal)
|
||||
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) {
|
||||
val assignments = if (assg.value is StructLiteralValue) {
|
||||
flattenStructAssignmentFromStructLiteral(assg, program) // 'structvar = { ..... } '
|
||||
} else {
|
||||
flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
// rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null) {
|
||||
if(binExpr.left isSameAs assignment.target) {
|
||||
// A = A <operator> 5, unchanged
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
if (binExpr.right isSameAs assignment.target) {
|
||||
// A = v <associative-operator> A ==> A = A <associative-operator> v
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
return if (assignments.isEmpty()) {
|
||||
// something went wrong (probably incompatible struct types)
|
||||
// we'll get an error later from the AstChecker
|
||||
assg
|
||||
} else {
|
||||
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
|
||||
scope.linkParents(assg.parent)
|
||||
scope
|
||||
|
||||
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||
if(leftBinExpr?.operator == binExpr.operator) {
|
||||
return if(leftBinExpr.left isSameAs assignment.target) {
|
||||
// A = (A <associative-operator> x) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position)
|
||||
val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
} else {
|
||||
// A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position)
|
||||
val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
}
|
||||
}
|
||||
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||
if(rightBinExpr?.operator == binExpr.operator) {
|
||||
return if(rightBinExpr.left isSameAs assignment.target) {
|
||||
// A = x <associative-operator> (A <same-operator> y) ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.right, binExpr.position)
|
||||
val newValue = BinaryExpression(rightBinExpr.left, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
} else {
|
||||
// A = x <associative-operator> (y <same-operator> A) ==> A = A <associative-operator> (x <same-operator> y)
|
||||
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.left, binExpr.position)
|
||||
val newValue = BinaryExpression(rightBinExpr.right, binExpr.operator, newRight, binExpr.position)
|
||||
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(assg.aug_op!=null) {
|
||||
// transform augmented assg into normal assg so we have one case less to deal with later
|
||||
val newTarget: Expression =
|
||||
when {
|
||||
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
|
||||
assg.target.identifier != null -> assg.target.identifier!!
|
||||
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
|
||||
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
|
||||
else -> throw FatalAstException("strange assg")
|
||||
}
|
||||
|
||||
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
|
||||
expression.linkParents(assg.parent)
|
||||
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
|
||||
convertedAssignment.linkParents(assg.parent)
|
||||
return super.visit(convertedAssignment)
|
||||
}
|
||||
|
||||
return assg
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
|
||||
@ -198,16 +173,16 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
val targetVar = identifier.targetVarDecl(program.namespace)!!
|
||||
val struct = targetVar.struct!!
|
||||
|
||||
val slv = structAssignment.value as? StructLiteralValue
|
||||
if(slv==null || slv.values.size != struct.numberOfElements)
|
||||
val slv = structAssignment.value as? ArrayLiteralValue
|
||||
if(slv==null || slv.value.size != struct.numberOfElements)
|
||||
throw FatalAstException("element count mismatch")
|
||||
|
||||
return struct.statements.zip(slv.values).map { (targetDecl, sourceValue) ->
|
||||
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
|
||||
targetDecl as VarDecl
|
||||
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
null, sourceValue, sourceValue.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
|
||||
sourceValue, sourceValue.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
@ -238,13 +213,12 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
|
||||
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
|
||||
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
|
||||
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
|
||||
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
|
||||
null, sourceIdref, member.second.position)
|
||||
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
|
||||
assign.linkParents(structAssignment)
|
||||
assign
|
||||
}
|
||||
}
|
||||
is StructLiteralValue -> {
|
||||
is ArrayLiteralValue -> {
|
||||
throw IllegalArgumentException("not going to flatten a structLv assignment here")
|
||||
}
|
||||
else -> throw FatalAstException("strange struct value")
|
||||
|
@ -4,9 +4,7 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.functions.BuiltinFunctions
|
||||
@ -18,6 +16,8 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
* (this includes function call arguments)
|
||||
*/
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
@ -34,7 +34,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
@ -45,13 +45,41 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
val targettype = targetItype.typeOrElse(DataType.STRUCT)
|
||||
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
|
||||
if (valuetype != targettype) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
assignment))
|
||||
if (valuetype isAssignableTo targettype) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
assignment.value,
|
||||
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
|
||||
assignment))
|
||||
} else {
|
||||
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> {
|
||||
val cast = cvalue.cast(targettype)
|
||||
return if(cast.isValid)
|
||||
listOf(IAstModification.ReplaceNode(cvalue, cast.valueOrZero(), cvalue.parent))
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
val cvalue = assignment.value.constValue(program)
|
||||
if(cvalue!=null) {
|
||||
val number = cvalue.number.toDouble()
|
||||
// more complex comparisons if the type is different, but the constant value is compatible
|
||||
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
|
||||
if(number>0)
|
||||
return castLiteral(cvalue)
|
||||
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
|
||||
if(number>0)
|
||||
return castLiteral(cvalue)
|
||||
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
|
||||
if(number<0x80)
|
||||
return castLiteral(cvalue)
|
||||
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
|
||||
if(number<0x8000)
|
||||
return castLiteral(cvalue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
@ -64,7 +92,9 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
|
||||
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
|
||||
// see if a typecast is needed to convert the arguments into the required parameter's type
|
||||
return when(val sub = call.target.targetStatement(scope)) {
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
when(val sub = call.target.targetStatement(scope)) {
|
||||
is Subroutine -> {
|
||||
for(arg in sub.parameters.zip(call.args.withIndex())) {
|
||||
val argItype = arg.second.value.inferType(program)
|
||||
@ -73,117 +103,81 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
val requiredType = arg.first.type
|
||||
if (requiredType != argtype) {
|
||||
if (argtype isAssignableTo requiredType) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
|
||||
call as Node))
|
||||
call as Node)
|
||||
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
|
||||
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
|
||||
call as Node)
|
||||
} else if(arg.second.value is NumericLiteralValue) {
|
||||
val cast = (arg.second.value as NumericLiteralValue).cast(requiredType)
|
||||
if(cast.isValid)
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
cast.valueOrZero(),
|
||||
call as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emptyList()
|
||||
}
|
||||
is BuiltinFunctionStatementPlaceholder -> {
|
||||
val func = BuiltinFunctions.getValue(sub.name)
|
||||
if(func.pure) {
|
||||
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
|
||||
for (arg in func.parameters.zip(call.args.withIndex())) {
|
||||
val argItype = arg.second.value.inferType(program)
|
||||
if (argItype.isKnown) {
|
||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||
if (arg.first.possibleDatatypes.any { argtype == it })
|
||||
continue
|
||||
for (possibleType in arg.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
|
||||
call as Node))
|
||||
}
|
||||
for (arg in func.parameters.zip(call.args.withIndex())) {
|
||||
val argItype = arg.second.value.inferType(program)
|
||||
if (argItype.isKnown) {
|
||||
val argtype = argItype.typeOrElse(DataType.STRUCT)
|
||||
if (arg.first.possibleDatatypes.any { argtype == it })
|
||||
continue
|
||||
for (possibleType in arg.first.possibleDatatypes) {
|
||||
if (argtype isAssignableTo possibleType) {
|
||||
modifications += IAstModification.ReplaceNode(
|
||||
call.args[arg.second.index],
|
||||
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
|
||||
call as Node)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emptyList()
|
||||
}
|
||||
null -> emptyList()
|
||||
else -> throw FatalAstException("call to something weird $sub ${call.target}")
|
||||
else -> { }
|
||||
}
|
||||
|
||||
return modifications
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// warn about any implicit type casts to Float, because that may not be intended
|
||||
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
|
||||
errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val dt = memread.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
|
||||
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
|
||||
// make sure the memory address is an uword
|
||||
val dt = memwrite.addressExpression.inferType(program)
|
||||
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
|
||||
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
|
||||
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
|
||||
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
|
||||
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
// assignment of a struct literal value, some member values may need proper typecast
|
||||
|
||||
fun addTypecastsIfNeeded(struct: StructDecl): Iterable<IAstModification> {
|
||||
val newValues = struct.statements.zip(structLv.values).map { (structMemberDecl, memberValue) ->
|
||||
val memberDt = (structMemberDecl as VarDecl).datatype
|
||||
val valueDt = memberValue.inferType(program)
|
||||
if (valueDt.typeOrElse(memberDt) != memberDt)
|
||||
TypecastExpression(memberValue, memberDt, true, memberValue.position)
|
||||
else
|
||||
memberValue
|
||||
}
|
||||
|
||||
class StructLvValueReplacer(val targetStructLv: StructLiteralValue, val typecastValues: List<Expression>) : IAstModification {
|
||||
override fun perform() {
|
||||
targetStructLv.values = typecastValues
|
||||
typecastValues.forEach { it.linkParents(targetStructLv) }
|
||||
}
|
||||
}
|
||||
|
||||
return if(structLv.values.zip(newValues).any { (v1, v2) -> v1 !== v2})
|
||||
listOf(StructLvValueReplacer(structLv, newValues))
|
||||
else
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val decl = structLv.parent as? VarDecl
|
||||
if(decl != null) {
|
||||
val struct = decl.struct
|
||||
if(struct != null)
|
||||
return addTypecastsIfNeeded(struct)
|
||||
} else {
|
||||
val assign = structLv.parent as? Assignment
|
||||
if (assign != null) {
|
||||
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
|
||||
if(decl2 != null) {
|
||||
val struct = decl2.struct
|
||||
if(struct != null)
|
||||
return addTypecastsIfNeeded(struct)
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
@ -194,9 +188,11 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
if(subroutine.returntypes.size==1) {
|
||||
val subReturnType = subroutine.returntypes.first()
|
||||
if (returnValue.inferType(program).istype(subReturnType))
|
||||
return emptyList()
|
||||
return noModifications
|
||||
if (returnValue is NumericLiteralValue) {
|
||||
returnStmt.value = returnValue.cast(subroutine.returntypes.single())
|
||||
val cast = returnValue.cast(subroutine.returntypes.single())
|
||||
if(cast.isValid)
|
||||
returnStmt.value = cast.valueOrZero()
|
||||
} else {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
returnValue,
|
||||
@ -205,6 +201,6 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
44
compiler/src/prog8/ast/processing/VariousCleanups.kt
Normal file
44
compiler/src/prog8/ast/processing/VariousCleanups.kt
Normal file
@ -0,0 +1,44 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.NopStatement
|
||||
|
||||
|
||||
internal class VariousCleanups: AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
|
||||
return listOf(IAstModification.Remove(nopStatement, parent))
|
||||
}
|
||||
|
||||
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
return if(parent is INameScope)
|
||||
listOf(ScopeFlatten(scope, parent as INameScope))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
|
||||
override fun perform() {
|
||||
val idx = into.statements.indexOf(scope)
|
||||
if(idx>=0) {
|
||||
into.statements.addAll(idx+1, scope.statements)
|
||||
into.statements.remove(scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.expression is NumericLiteralValue) {
|
||||
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
|
||||
if(value.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
61
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
61
compiler/src/prog8/ast/processing/VerifyFunctionArgTypes.kt
Normal file
@ -0,0 +1,61 @@
|
||||
package prog8.ast.processing
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.expressions.FunctionCall
|
||||
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(functionCall: FunctionCall) {
|
||||
val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program)
|
||||
if(error!=null)
|
||||
throw CompilerException(error)
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement) {
|
||||
val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program)
|
||||
if (error!=null)
|
||||
throw CompilerException(error)
|
||||
}
|
||||
|
||||
companion object {
|
||||
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)
|
||||
if (target is Subroutine) {
|
||||
// asmsub types are not checked specifically at this time
|
||||
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}
|
||||
if(mismatch>=0) {
|
||||
val actual = argtypes[mismatch].toString()
|
||||
val expected = paramtypes[mismatch].toString()
|
||||
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
|
||||
}
|
||||
}
|
||||
else if (target is BuiltinFunctionStatementPlaceholder) {
|
||||
val func = BuiltinFunctions.getValue(target.name)
|
||||
if(call.args.size != func.parameters.size)
|
||||
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 actual = x.value.first.toString()
|
||||
val expected = x.value.second.toString()
|
||||
return "argument ${x.index + 1} type mismatch, was: $actual expected: $expected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -4,12 +4,10 @@ import prog8.ast.*
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
|
||||
|
||||
sealed class Statement : Node {
|
||||
abstract fun accept(visitor: IAstModifyingVisitor) : Statement
|
||||
abstract fun accept(visitor: IAstVisitor)
|
||||
abstract fun accept(visitor: AstWalker, parent: Node)
|
||||
|
||||
@ -31,8 +29,6 @@ sealed class Statement : Node {
|
||||
return scope.joinToString(".")
|
||||
}
|
||||
|
||||
abstract val expensiveToInline: Boolean
|
||||
|
||||
fun definingBlock(): Block {
|
||||
if(this is Block)
|
||||
return this
|
||||
@ -44,12 +40,12 @@ sealed class Statement : Node {
|
||||
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
|
||||
override var parent: Node = ParentSentinel
|
||||
override fun linkParents(parent: Node) {}
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {}
|
||||
override val expensiveToInline = false
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
replacement.parent = this
|
||||
}
|
||||
}
|
||||
|
||||
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
|
||||
@ -60,8 +56,6 @@ class Block(override val name: String,
|
||||
val isInLibrary: Boolean,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = statements.any { it.expensiveToInline }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -70,11 +64,11 @@ class Block(override val name: String,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it ===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -87,7 +81,6 @@ class Block(override val name: String,
|
||||
|
||||
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -95,7 +88,6 @@ data class Directive(val directive: String, val args: List<DirectiveArg>, overri
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -111,14 +103,12 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
|
||||
|
||||
data class Label(val name: String, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -129,7 +119,6 @@ data class Label(val name: String, override val position: Position) : Statement(
|
||||
|
||||
open class Return(var value: Expression?, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = value!=null && value !is NumericLiteralValue
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -139,9 +128,9 @@ open class Return(var value: Expression?, override val position: Position) : Sta
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression)
|
||||
value = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -150,40 +139,14 @@ open class Return(var value: Expression?, override val position: Position) : Sta
|
||||
}
|
||||
}
|
||||
|
||||
class ReturnFromIrq(override val position: Position) : Return(null, position) {
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
|
||||
override fun toString(): String {
|
||||
return "ReturnFromIrq(pos=$position)"
|
||||
}
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
}
|
||||
|
||||
class Continue(override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class Break(override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -213,9 +176,6 @@ open class VarDecl(val type: VarDeclType,
|
||||
var structHasBeenFlattened = false // set later
|
||||
private set
|
||||
|
||||
override val expensiveToInline
|
||||
get() = value!=null && value !is NumericLiteralValue
|
||||
|
||||
// prefix for literal values that are turned into a variable on the heap
|
||||
|
||||
companion object {
|
||||
@ -274,9 +234,9 @@ open class VarDecl(val type: VarDeclType,
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===value)
|
||||
value = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -289,7 +249,7 @@ open class VarDecl(val type: VarDeclType,
|
||||
fun flattenStructMembers(): MutableList<Statement> {
|
||||
val result = struct!!.statements.withIndex().map {
|
||||
val member = it.value as VarDecl
|
||||
val initvalue = if(value!=null) (value as StructLiteralValue).values[it.index] else null
|
||||
val initvalue = if(value!=null) (value as ArrayLiteralValue).value[it.index] else null
|
||||
VarDecl(
|
||||
VarDeclType.VAR,
|
||||
member.datatype,
|
||||
@ -301,8 +261,8 @@ open class VarDecl(val type: VarDeclType,
|
||||
member.isArray,
|
||||
true,
|
||||
member.position
|
||||
) as Statement
|
||||
}.toMutableList()
|
||||
)
|
||||
}.toMutableList<Statement>()
|
||||
structHasBeenFlattened = true
|
||||
return result
|
||||
}
|
||||
@ -310,7 +270,7 @@ open class VarDecl(val type: VarDeclType,
|
||||
|
||||
// a vardecl used only for subroutine parameters
|
||||
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
|
||||
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.NOT_IN_ZEROPAGE, null, name, null, null, false, true, position)
|
||||
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position)
|
||||
|
||||
|
||||
class ArrayIndex(var index: Expression, override val position: Position) : Node {
|
||||
@ -324,6 +284,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===index)
|
||||
index = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -332,23 +293,18 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
|
||||
}
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstModifyingVisitor) {
|
||||
index = index.accept(visitor)
|
||||
}
|
||||
fun accept(visitor: IAstVisitor) = index.accept(visitor)
|
||||
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, parent)
|
||||
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
|
||||
|
||||
override fun toString(): String {
|
||||
return("ArrayIndex($index, pos=$position)")
|
||||
}
|
||||
|
||||
fun size() = (index as? NumericLiteralValue)?.number?.toInt()
|
||||
fun constIndex() = (index as? NumericLiteralValue)?.number?.toInt()
|
||||
}
|
||||
|
||||
open class Assignment(var target: AssignTarget, val aug_op : String?, var value: Expression, override val position: Position) : Statement() {
|
||||
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = value !is NumericLiteralValue
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -362,19 +318,65 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
|
||||
node===value -> value = replacement as Expression
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
|
||||
return("Assignment(target: $target, value: $value, pos=$position)")
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the assigment value an expression that references the assignment target itself?
|
||||
* The expression can be a BinaryExpression, PrefixExpression or TypecastExpression (possibly with one sub-cast).
|
||||
*/
|
||||
val isAugmentable: Boolean
|
||||
get() {
|
||||
val binExpr = value as? BinaryExpression
|
||||
if(binExpr!=null) {
|
||||
if(binExpr.left isSameAs target)
|
||||
return true // A = A <operator> Something
|
||||
|
||||
if(binExpr.operator in associativeOperators) {
|
||||
if (binExpr.left !is BinaryExpression && binExpr.right isSameAs target)
|
||||
return true // A = v <associative-operator> A
|
||||
|
||||
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||
if(leftBinExpr?.operator == binExpr.operator) {
|
||||
// one of these?
|
||||
// A = (A <associative-operator> x) <same-operator> y
|
||||
// A = (x <associative-operator> A) <same-operator> y
|
||||
// A = (x <associative-operator> y) <same-operator> A
|
||||
return leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target || binExpr.right isSameAs target
|
||||
}
|
||||
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||
if(rightBinExpr?.operator == binExpr.operator) {
|
||||
// one of these?
|
||||
// A = y <associative-operator> (A <same-operator> x)
|
||||
// A = y <associative-operator> (x <same-operator> y)
|
||||
// A = A <associative-operator> (x <same-operator> y)
|
||||
return rightBinExpr.left isSameAs target || rightBinExpr.right isSameAs target || binExpr.left isSameAs target
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val prefixExpr = value as? PrefixExpression
|
||||
if(prefixExpr!=null)
|
||||
return prefixExpr.expression isSameAs target
|
||||
|
||||
val castExpr = value as? TypecastExpression
|
||||
if(castExpr!=null) {
|
||||
val subCast = castExpr.expression as? TypecastExpression
|
||||
return if(subCast!=null) subCast.expression isSameAs target else castExpr.expression isSameAs target
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
data class AssignTarget(val register: Register?,
|
||||
var identifier: IdentifierReference?,
|
||||
data class AssignTarget(var identifier: IdentifierReference?,
|
||||
var arrayindexed: ArrayIndexedExpression?,
|
||||
val memoryAddress: DirectMemoryWrite?,
|
||||
override val position: Position) : Node {
|
||||
@ -393,28 +395,24 @@ data class AssignTarget(val register: Register?,
|
||||
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
companion object {
|
||||
fun fromExpr(expr: Expression): AssignTarget {
|
||||
return when (expr) {
|
||||
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
|
||||
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
|
||||
is ArrayIndexedExpression -> AssignTarget(null, null, expr, null, expr.position)
|
||||
is DirectMemoryRead -> AssignTarget(null, null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
|
||||
is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
|
||||
is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
|
||||
is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
|
||||
else -> throw FatalAstException("invalid expression object $expr")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
|
||||
if(register!=null)
|
||||
return InferredTypes.knownFor(DataType.UBYTE)
|
||||
|
||||
if(identifier!=null) {
|
||||
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
|
||||
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
|
||||
@ -430,6 +428,15 @@ data class AssignTarget(val register: Register?,
|
||||
return InferredTypes.unknown()
|
||||
}
|
||||
|
||||
fun toExpression(): Expression {
|
||||
return when {
|
||||
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 -> {
|
||||
@ -439,13 +446,12 @@ data class AssignTarget(val register: Register?,
|
||||
else
|
||||
false
|
||||
}
|
||||
this.register!=null -> value is RegisterExpr && value.register==register
|
||||
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
|
||||
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
||||
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
|
||||
value.arrayspec.size()!=null &&
|
||||
arrayindexed!!.arrayspec.size()!=null &&
|
||||
value.arrayspec.size()==arrayindexed!!.arrayspec.size()
|
||||
value.arrayspec.constIndex()!=null &&
|
||||
arrayindexed!!.arrayspec.constIndex()!=null &&
|
||||
value.arrayspec.constIndex()==arrayindexed!!.arrayspec.constIndex()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -453,8 +459,6 @@ data class AssignTarget(val register: Register?,
|
||||
fun isSameAs(other: AssignTarget, program: Program): Boolean {
|
||||
if(this===other)
|
||||
return true
|
||||
if(this.register!=null && other.register!=null)
|
||||
return this.register==other.register
|
||||
if(this.identifier!=null && other.identifier!=null)
|
||||
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
|
||||
if(this.memoryAddress!=null && other.memoryAddress!=null) {
|
||||
@ -473,8 +477,6 @@ data class AssignTarget(val register: Register?,
|
||||
}
|
||||
|
||||
fun isNotMemory(namespace: INameScope): Boolean {
|
||||
if(this.register!=null)
|
||||
return true
|
||||
if(this.memoryAddress!=null)
|
||||
return false
|
||||
if(this.arrayindexed!=null) {
|
||||
@ -493,7 +495,6 @@ data class AssignTarget(val register: Register?,
|
||||
|
||||
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -503,9 +504,9 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AssignTarget && node===target)
|
||||
target = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -519,7 +520,6 @@ class Jump(val address: Int?,
|
||||
val generatedLabel: String?, // used in code generation scenarios
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -527,7 +527,6 @@ class Jump(val address: Int?,
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -541,8 +540,6 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
val void: Boolean,
|
||||
override val position: Position) : Statement(), IFunctionCall {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = args.any { it !is NumericLiteralValue }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -554,12 +551,12 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
if(node===target)
|
||||
target = replacement as IdentifierReference
|
||||
else {
|
||||
val idx = args.indexOf(node)
|
||||
val idx = args.indexOfFirst { it===node }
|
||||
args[idx] = replacement as Expression
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -570,14 +567,12 @@ class FunctionCallStatement(override var target: IdentifierReference,
|
||||
|
||||
class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -586,8 +581,6 @@ class AnonymousScope(override var statements: MutableList<Statement>,
|
||||
override val position: Position) : INameScope, Statement() {
|
||||
override val name: String
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline
|
||||
get() = statements.any { it.expensiveToInline }
|
||||
|
||||
companion object {
|
||||
private var sequenceNumber = 1
|
||||
@ -605,35 +598,25 @@ class AnonymousScope(override var statements: MutableList<Statement>,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class NopStatement(override val position: Position): Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = false
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
companion object {
|
||||
fun insteadOf(stmt: Statement): NopStatement {
|
||||
val nop = NopStatement(stmt.position)
|
||||
nop.parent = stmt.parent
|
||||
return nop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the subroutine class covers both the normal user-defined subroutines,
|
||||
@ -644,20 +627,13 @@ class Subroutine(override val name: String,
|
||||
val returntypes: List<DataType>,
|
||||
val asmParameterRegisters: List<RegisterOrStatusflag>,
|
||||
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
|
||||
val asmClobbers: Set<Register>,
|
||||
val asmClobbers: Set<CpuRegister>,
|
||||
val asmAddress: Int?,
|
||||
val isAsmSubroutine: Boolean,
|
||||
override var statements: MutableList<Statement>,
|
||||
override val position: Position) : Statement(), INameScope {
|
||||
|
||||
var keepAlways: Boolean = false
|
||||
override val expensiveToInline
|
||||
get() = statements.any { it.expensiveToInline }
|
||||
|
||||
override lateinit var parent: Node
|
||||
val calledBy = mutableListOf<Node>()
|
||||
val calls = mutableSetOf<Subroutine>()
|
||||
|
||||
val scopedname: String by lazy { makeScopedName(name) }
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
@ -668,11 +644,11 @@ class Subroutine(override val name: String,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -680,6 +656,9 @@ class Subroutine(override val name: String,
|
||||
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
|
||||
}
|
||||
|
||||
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
fun regXasParam() = asmParameterRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
|
||||
fun amountOfRtsInAsm(): Int = statements
|
||||
.asSequence()
|
||||
.filter { it is InlineAssembly }
|
||||
@ -707,8 +686,6 @@ class IfStatement(var condition: Expression,
|
||||
var elsepart: AnonymousScope,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean
|
||||
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -724,9 +701,9 @@ class IfStatement(var condition: Expression,
|
||||
node===elsepart -> elsepart = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
@ -737,8 +714,6 @@ class BranchStatement(var condition: BranchCondition,
|
||||
var elsepart: AnonymousScope,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean
|
||||
get() = truepart.expensiveToInline || elsepart.expensiveToInline
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -752,25 +727,23 @@ class BranchStatement(var condition: BranchCondition,
|
||||
node===elsepart -> elsepart = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
}
|
||||
|
||||
class ForLoop(val loopRegister: Register?,
|
||||
var loopVar: IdentifierReference?,
|
||||
class ForLoop(var loopVar: IdentifierReference,
|
||||
var iterable: Expression,
|
||||
var body: AnonymousScope,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent=parent
|
||||
loopVar?.linkParents(this)
|
||||
loopVar.linkParents(this)
|
||||
iterable.linkParents(this)
|
||||
body.linkParents(this)
|
||||
}
|
||||
@ -782,28 +755,23 @@ class ForLoop(val loopRegister: Register?,
|
||||
node===body -> body = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
override fun toString(): String {
|
||||
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
|
||||
return "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
|
||||
}
|
||||
|
||||
fun loopVarDt(program: Program): InferredTypes.InferredType {
|
||||
val lv = loopVar
|
||||
return if(loopRegister!=null) InferredTypes.InferredType.known(DataType.UBYTE)
|
||||
else lv?.inferType(program) ?: InferredTypes.InferredType.unknown()
|
||||
}
|
||||
fun loopVarDt(program: Program) = loopVar.inferType(program)
|
||||
}
|
||||
|
||||
class WhileLoop(var condition: Expression,
|
||||
var body: AnonymousScope,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -817,37 +785,39 @@ class WhileLoop(var condition: Expression,
|
||||
node===body -> body = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class ForeverLoop(var body: AnonymousScope, override val position: Position) : Statement() {
|
||||
class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
iterations?.linkParents(this)
|
||||
body.linkParents(this)
|
||||
}
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AnonymousScope && node===body)
|
||||
body = replacement
|
||||
when {
|
||||
node===iterations -> iterations = replacement as Expression
|
||||
node===body -> body = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
class RepeatLoop(var body: AnonymousScope,
|
||||
var untilCondition: Expression,
|
||||
override val position: Position) : Statement() {
|
||||
class UntilLoop(var body: AnonymousScope,
|
||||
var untilCondition: Expression,
|
||||
override val position: Position) : Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -861,9 +831,9 @@ class RepeatLoop(var body: AnonymousScope,
|
||||
node===body -> body = replacement as AnonymousScope
|
||||
else -> throw FatalAstException("invalid replace")
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
@ -872,7 +842,6 @@ class WhenStatement(var condition: Expression,
|
||||
var choices: MutableList<WhenChoice>,
|
||||
override val position: Position): Statement() {
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -884,9 +853,10 @@ class WhenStatement(var condition: Expression,
|
||||
if(node===condition)
|
||||
condition = replacement as Expression
|
||||
else {
|
||||
val idx = choices.indexOf(node)
|
||||
val idx = choices.withIndex().find { it.value===node }!!.index
|
||||
choices[idx] = replacement as WhenChoice
|
||||
}
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
|
||||
@ -907,7 +877,6 @@ class WhenStatement(var condition: Expression,
|
||||
}
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
@ -925,6 +894,7 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is AnonymousScope && node===statements)
|
||||
statements = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -932,7 +902,6 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
||||
@ -942,7 +911,6 @@ class StructDecl(override val name: String,
|
||||
override val position: Position): Statement(), INameScope {
|
||||
|
||||
override lateinit var parent: Node
|
||||
override val expensiveToInline: Boolean = true
|
||||
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
@ -951,15 +919,15 @@ class StructDecl(override val name: String,
|
||||
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Statement)
|
||||
val idx = statements.indexOf(node)
|
||||
val idx = statements.indexOfFirst { it===node }
|
||||
statements[idx] = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
val numberOfElements: Int
|
||||
get() = this.statements.size
|
||||
|
||||
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
|
||||
fun nameOfFirstMember() = (statements.first() as VarDecl).name
|
||||
@ -976,6 +944,7 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
|
||||
override fun replaceChildNode(node: Node, replacement: Node) {
|
||||
require(replacement is Expression && node===addressExpression)
|
||||
addressExpression = replacement
|
||||
replacement.parent = this
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
@ -983,6 +952,5 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
|
||||
}
|
||||
|
||||
fun accept(visitor: IAstVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
|
||||
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
|
||||
}
|
||||
|
132
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
132
compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt
Normal file
@ -0,0 +1,132 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if (decl.value == null && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// a numeric vardecl without an initial value is initialized with zero.
|
||||
decl.value = decl.zeroElementValue()
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(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.
|
||||
if(!assignment.isAugmentable
|
||||
&& assignment.target.identifier != null
|
||||
&& assignment.target.isNotMemory(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))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||
val sub = scope.definingSubroutine()
|
||||
if (sub != null) {
|
||||
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
||||
var conflicts = false
|
||||
decls.forEach {
|
||||
val existing = existingVariables[it.name]
|
||||
if (existing != null) {
|
||||
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
|
||||
conflicts = true
|
||||
}
|
||||
}
|
||||
if (!conflicts) {
|
||||
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
|
||||
return numericVarsWithValue.map {
|
||||
val initValue = it.value!! // assume here that value has always been set by now
|
||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||
val assign = Assignment(target, initValue, it.position)
|
||||
initValue.parent = assign
|
||||
IAstModification.InsertFirst(assign, scope)
|
||||
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
|
||||
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
if (subroutine.asmAddress == null
|
||||
&& subroutine.statements.isNotEmpty()
|
||||
&& subroutine.amountOfRtsInAsm() == 0
|
||||
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine) {
|
||||
mods += IAstModification.InsertLast(returnStmt, subroutine)
|
||||
}
|
||||
|
||||
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
|
||||
val outerScope = subroutine.definingScope()
|
||||
val outerStatements = outerScope.statements
|
||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||
if (subroutineStmtIdx > 0
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Jump
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
|
||||
&& outerStatements[subroutineStmtIdx - 1] !is Return
|
||||
&& outerScope !is Block) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
// see if we can remove superfluous typecasts (outside of expressions)
|
||||
// such as casting byte<->ubyte, word<->uword
|
||||
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
|
||||
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|
||||
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
|
||||
if(typecast.parent !is Expression) {
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
|
||||
// that the types of assignment values and their target are the same,
|
||||
// and that the types of both operands of a binaryexpression node are the same.
|
||||
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
|
||||
|
||||
if(sourceDt in PassByReferenceDatatypes) {
|
||||
if(typecast.type==DataType.UWORD) {
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
typecast,
|
||||
AddressOf(typecast.expression as IdentifierReference, typecast.position),
|
||||
parent
|
||||
))
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.optimizer.UnusedCodeRemover
|
||||
import prog8.optimizer.constantFold
|
||||
import prog8.optimizer.optimizeStatements
|
||||
import prog8.optimizer.simplifyExpressions
|
||||
@ -46,6 +47,8 @@ fun compileProgram(filepath: Path,
|
||||
if(writeAssembly)
|
||||
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
|
||||
}
|
||||
System.out.flush()
|
||||
System.err.flush()
|
||||
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
|
||||
return CompilationResult(true, programAst, programName, importedFiles)
|
||||
|
||||
@ -76,7 +79,7 @@ fun compileProgram(filepath: Path,
|
||||
|
||||
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
|
||||
println("Parsing...")
|
||||
val importer = ModuleImporter(errors)
|
||||
val importer = ModuleImporter()
|
||||
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||
importer.importModule(programAst, filepath)
|
||||
errors.handle()
|
||||
@ -87,11 +90,8 @@ private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program,
|
||||
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
|
||||
|
||||
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
|
||||
importer.importLibraryModule(programAst, "c64lib")
|
||||
importer.importLibraryModule(programAst, "c64utils")
|
||||
}
|
||||
// depending on the mach9ine and compiler options we may have to include some libraries
|
||||
CompilationTarget.machine.importLibs(compilerOptions, importer, programAst)
|
||||
|
||||
// always import prog8lib and math
|
||||
importer.importLibraryModule(programAst, "math")
|
||||
@ -141,13 +141,12 @@ private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptio
|
||||
println("Processing...")
|
||||
programAst.checkIdentifiers(errors)
|
||||
errors.handle()
|
||||
programAst.makeForeverLoops()
|
||||
programAst.constantFold(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.reorderStatements()
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors)
|
||||
errors.handle()
|
||||
programAst.checkIdentifiers(errors)
|
||||
@ -161,37 +160,46 @@ 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:
|
||||
errors.handle()
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
break
|
||||
}
|
||||
// because simplified statements and expressions could give rise to more constants that can be folded away:
|
||||
programAst.constantFold(errors)
|
||||
|
||||
val remover = UnusedCodeRemover(errors)
|
||||
remover.visit(programAst)
|
||||
remover.applyModifications()
|
||||
errors.handle()
|
||||
}
|
||||
|
||||
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
|
||||
programAst.addTypecasts(errors)
|
||||
errors.handle()
|
||||
programAst.removeNopsFlattenAnonScopes()
|
||||
programAst.variousCleanups()
|
||||
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
|
||||
errors.handle()
|
||||
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
|
||||
errors.handle()
|
||||
programAst.verifyFunctionArgTypes()
|
||||
}
|
||||
|
||||
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
|
||||
optimize: Boolean, compilerOptions: CompilationOptions): String {
|
||||
// asm generation directly from the Ast,
|
||||
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
|
||||
programAst.prepareAsmVariablesAndReturns(errors)
|
||||
programAst.processAstBeforeAsmGeneration(errors)
|
||||
errors.handle()
|
||||
|
||||
// printAst(programAst)
|
||||
|
||||
CompilationTarget.machine.initializeZeropage(compilerOptions)
|
||||
val assembly = CompilationTarget.asmGenerator(
|
||||
programAst,
|
||||
zeropage,
|
||||
errors,
|
||||
CompilationTarget.machine.zeropage,
|
||||
compilerOptions,
|
||||
outputDir).compileToAssembly(optimize)
|
||||
assembly.assemble(compilerOptions)
|
||||
errors.handle()
|
||||
return assembly.name
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,13 @@ class ZeropageDepletedError(message: String) : Exception(message)
|
||||
|
||||
abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
abstract val SCRATCH_B1 : Int // temp storage for a single byte
|
||||
abstract val SCRATCH_REG : Int // temp storage for a register
|
||||
abstract val SCRATCH_REG_X : Int // temp storage for register X (the evaluation stack pointer)
|
||||
abstract val SCRATCH_W1 : Int // temp storage 1 for a word $fb+$fc
|
||||
abstract val SCRATCH_W2 : Int // temp storage 2 for a word $fb+$fc
|
||||
|
||||
|
||||
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
|
||||
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
@ -16,7 +23,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
|
||||
|
||||
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
|
||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
|
||||
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
|
||||
|
||||
if(options.zeropage==ZeropageType.DONTUSE)
|
||||
throw CompilerException("zero page usage has been disabled")
|
||||
@ -39,13 +46,13 @@ abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
if(free.size > 0) {
|
||||
if(size==1) {
|
||||
for(candidate in free.min()!! .. free.max()!!+1) {
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
|
||||
if(loneByte(candidate))
|
||||
return makeAllocation(candidate, 1, datatype, scopedname)
|
||||
}
|
||||
return makeAllocation(free[0], 1, datatype, scopedname)
|
||||
}
|
||||
for(candidate in free.min()!! .. free.max()!!+1) {
|
||||
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
|
||||
if (sequentialFree(candidate, size))
|
||||
return makeAllocation(candidate, size, datatype, scopedname)
|
||||
}
|
||||
|
@ -1,80 +0,0 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.NumericDatatypes
|
||||
import prog8.ast.base.VarDeclType
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
|
||||
class AsmVariableAndReturnsPreparer(val program: Program, val errors: ErrorReporter): AstWalker() {
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(decl.value==null && decl.type==VarDeclType.VAR && decl.datatype in NumericDatatypes) {
|
||||
// a numeric vardecl without an initial value is initialized with zero.
|
||||
decl.value = decl.zeroElementValue()
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val decls = scope.statements.filterIsInstance<VarDecl>()
|
||||
val sub = scope.definingSubroutine()
|
||||
if(sub!=null) {
|
||||
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
|
||||
var conflicts = false
|
||||
decls.forEach {
|
||||
val existing = existingVariables[it.name]
|
||||
if (existing!=null) {
|
||||
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
|
||||
conflicts = true
|
||||
}
|
||||
}
|
||||
if(!conflicts) {
|
||||
val numericVarsWithValue = decls.filter { it.value!=null && it.datatype in NumericDatatypes }
|
||||
return numericVarsWithValue.map {
|
||||
val initValue = it.value!! // assume here that value has always been set by now
|
||||
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
|
||||
val target = AssignTarget(null, IdentifierReference(listOf(it.name), it.position), null, null, it.position)
|
||||
val assign = Assignment(target, null, initValue, it.position)
|
||||
IAstModification.InsertFirst(assign, scope)
|
||||
} +
|
||||
decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
|
||||
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
|
||||
// and if an assembly block doesn't contain a rts/rti, and some other situations.
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
val returnStmt = Return(null, subroutine.position)
|
||||
if(subroutine.asmAddress==null
|
||||
&& subroutine.statements.isNotEmpty()
|
||||
&& subroutine.amountOfRtsInAsm()==0
|
||||
&& subroutine.statements.lastOrNull {it !is VarDecl } !is Return
|
||||
&& subroutine.statements.last() !is Subroutine) {
|
||||
mods += IAstModification.InsertAfter(subroutine.statements.last(), returnStmt, subroutine)
|
||||
}
|
||||
|
||||
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
|
||||
val outerScope = subroutine.definingScope()
|
||||
val outerStatements = outerScope.statements
|
||||
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
|
||||
if(subroutineStmtIdx>0
|
||||
&& outerStatements[subroutineStmtIdx-1] !is Jump
|
||||
&& outerStatements[subroutineStmtIdx-1] !is Subroutine
|
||||
&& outerStatements[subroutineStmtIdx-1] !is Return
|
||||
&& outerScope !is Block) {
|
||||
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx-1], returnStmt, outerScope as Node)
|
||||
}
|
||||
|
||||
return mods
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
import java.nio.file.Path
|
||||
@ -12,6 +13,6 @@ internal interface CompilationTarget {
|
||||
lateinit var machine: IMachineDefinition
|
||||
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
|
||||
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
|
||||
lateinit var asmGenerator: (Program, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
|
||||
lateinit var asmGenerator: (Program, ErrorReporter, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,39 @@
|
||||
package prog8.compiler.target
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.parser.ModuleImporter
|
||||
|
||||
|
||||
interface IMachineDefinition {
|
||||
internal interface IMachineFloat {
|
||||
fun toDouble(): Double
|
||||
fun makeFloatFillAsm(): String
|
||||
}
|
||||
|
||||
internal enum class CpuType {
|
||||
CPU6502,
|
||||
CPU65c02
|
||||
}
|
||||
|
||||
internal interface IMachineDefinition {
|
||||
val FLOAT_MAX_NEGATIVE: Double
|
||||
val FLOAT_MAX_POSITIVE: Double
|
||||
val FLOAT_MEM_SIZE: Int
|
||||
val POINTER_MEM_SIZE: Int
|
||||
val ESTACK_LO: Int
|
||||
val ESTACK_HI: Int
|
||||
val BASIC_LOAD_ADDRESS : Int
|
||||
val RAW_LOAD_ADDRESS : Int
|
||||
|
||||
val opcodeNames: Set<String>
|
||||
var zeropage: Zeropage
|
||||
val initSystemProcname: String
|
||||
val cpu: CpuType
|
||||
|
||||
fun getZeropage(compilerOptions: CompilationOptions): Zeropage
|
||||
fun initializeZeropage(compilerOptions: CompilationOptions)
|
||||
fun getFloat(num: Number): IMachineFloat
|
||||
fun getFloatRomConst(number: Double): String?
|
||||
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
|
||||
fun launchEmulator(programName: String)
|
||||
}
|
||||
|
@ -14,15 +14,15 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
|
||||
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
|
||||
|
||||
override fun assemble(options: CompilationOptions) {
|
||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
|
||||
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
|
||||
|
||||
val outFile = when (options.output) {
|
||||
OutputType.PRG -> {
|
||||
command.add("--cbm-prg")
|
||||
println("\nCreating C-64 prg.")
|
||||
println("\nCreating prg.")
|
||||
prgFile
|
||||
}
|
||||
OutputType.RAW -> {
|
||||
|
@ -1,37 +1,97 @@
|
||||
package prog8.compiler.target.c64
|
||||
|
||||
import prog8.compiler.CompilationOptions
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.Zeropage
|
||||
import prog8.compiler.ZeropageType
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.IMachineDefinition
|
||||
import java.awt.Color
|
||||
import java.awt.image.BufferedImage
|
||||
import javax.imageio.ImageIO
|
||||
import prog8.compiler.target.IMachineFloat
|
||||
import prog8.parser.ModuleImporter
|
||||
import java.io.IOException
|
||||
import java.math.RoundingMode
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.pow
|
||||
|
||||
object C64MachineDefinition: IMachineDefinition {
|
||||
internal object C64MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU6502
|
||||
|
||||
// 5-byte cbm MFLPT format limitations:
|
||||
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
override val FLOAT_MEM_SIZE = 5
|
||||
const val BASIC_LOAD_ADDRESS = 0x0801
|
||||
const val RAW_LOAD_ADDRESS = 0xc000
|
||||
override val POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||
override val RAW_LOAD_ADDRESS = 0xc000
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
// and some heavily used string constants derived from the two values above
|
||||
const val ESTACK_LO_VALUE = 0xce00 // $ce00-$ceff inclusive
|
||||
const val ESTACK_HI_VALUE = 0xcf00 // $cf00-$cfff inclusive
|
||||
const val ESTACK_LO_HEX = "\$ce00"
|
||||
const val ESTACK_LO_PLUS1_HEX = "\$ce01"
|
||||
const val ESTACK_LO_PLUS2_HEX = "\$ce02"
|
||||
const val ESTACK_HI_HEX = "\$cf00"
|
||||
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
|
||||
const val ESTACK_HI_PLUS2_HEX = "\$cf02"
|
||||
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
|
||||
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
|
||||
|
||||
override fun getZeropage(compilerOptions: CompilationOptions) = C64Zeropage(compilerOptions)
|
||||
override lateinit var zeropage: Zeropage
|
||||
override val initSystemProcname = "c64.init_system"
|
||||
|
||||
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
|
||||
|
||||
override fun getFloatRomConst(number: Double): String? {
|
||||
// try to match the ROM float constants to save memory
|
||||
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"
|
||||
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"
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
|
||||
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
|
||||
importer.importLibraryModule(program, "c64lib")
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
for(emulator in listOf("x64sc", "x64")) {
|
||||
println("\nStarting C-64 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
process=processb.start()
|
||||
} catch(x: IOException) {
|
||||
continue // try the next emulator executable
|
||||
}
|
||||
process.waitFor()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = C64Zeropage(compilerOptions)
|
||||
}
|
||||
|
||||
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
|
||||
@ -45,15 +105,14 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
|
||||
|
||||
|
||||
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x03 // temp storage for a register
|
||||
override val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
|
||||
override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
|
||||
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
|
||||
|
||||
companion object {
|
||||
const val SCRATCH_B1 = 0x02
|
||||
const val SCRATCH_REG = 0x03 // temp storage for a register
|
||||
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
|
||||
const val SCRATCH_W1 = 0xfb // $fb+$fc
|
||||
const val SCRATCH_W2 = 0xfd // $fd+$fe
|
||||
}
|
||||
|
||||
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
|
||||
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
|
||||
@ -109,19 +168,18 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
free.clear()
|
||||
}
|
||||
}
|
||||
assert(SCRATCH_B1 !in free)
|
||||
assert(SCRATCH_REG !in free)
|
||||
assert(SCRATCH_REG_X !in free)
|
||||
assert(SCRATCH_W1 !in free)
|
||||
assert(SCRATCH_W2 !in free)
|
||||
require(SCRATCH_B1 !in free)
|
||||
require(SCRATCH_REG !in free)
|
||||
require(SCRATCH_REG_X !in free)
|
||||
require(SCRATCH_W1 !in free)
|
||||
require(SCRATCH_W2 !in free)
|
||||
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
|
||||
internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat {
|
||||
|
||||
companion object {
|
||||
val zero = Mflpt5(0, 0, 0, 0, 0)
|
||||
@ -168,7 +226,7 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
fun toDouble(): Double {
|
||||
override fun toDouble(): Double {
|
||||
if (this == zero) return 0.0
|
||||
val exp = b0 - 128
|
||||
val sign = (b1.toInt() and 0x80) > 0
|
||||
@ -176,91 +234,14 @@ object C64MachineDefinition: IMachineDefinition {
|
||||
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
|
||||
return if (sign) -result else result
|
||||
}
|
||||
}
|
||||
|
||||
object Charset {
|
||||
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
|
||||
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
|
||||
|
||||
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
|
||||
|
||||
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
|
||||
transparent.createGraphics().drawImage(img, 0, 0, null)
|
||||
|
||||
val black = Color(0, 0, 0).rgb
|
||||
val nopixel = Color(0, 0, 0, 0).rgb
|
||||
for (y in 0 until transparent.height) {
|
||||
for (x in 0 until transparent.width) {
|
||||
val col = transparent.getRGB(x, y)
|
||||
if (col == black)
|
||||
transparent.setRGB(x, y, nopixel)
|
||||
}
|
||||
}
|
||||
|
||||
val numColumns = transparent.width / 8
|
||||
val charImages = (0..255).map {
|
||||
val charX = it % numColumns
|
||||
val charY = it / numColumns
|
||||
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
|
||||
}
|
||||
return charImages.toTypedArray()
|
||||
override fun makeFloatFillAsm(): String {
|
||||
val b0 = "$" + b0.toString(16).padStart(2, '0')
|
||||
val b1 = "$" + b1.toString(16).padStart(2, '0')
|
||||
val b2 = "$" + b2.toString(16).padStart(2, '0')
|
||||
val b3 = "$" + b3.toString(16).padStart(2, '0')
|
||||
val b4 = "$" + b4.toString(16).padStart(2, '0')
|
||||
return "$b0, $b1, $b2, $b3, $b4"
|
||||
}
|
||||
|
||||
val normalChars = scanChars(normalImg)
|
||||
val shiftedChars = scanChars(shiftedImg)
|
||||
|
||||
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
|
||||
|
||||
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
|
||||
val colorIdx = (color % colorPalette.size).toShort()
|
||||
val chars = coloredNormalChars[colorIdx]
|
||||
if (chars != null)
|
||||
return chars[screenCode.toInt()]
|
||||
|
||||
val coloredChars = mutableListOf<BufferedImage>()
|
||||
val transparent = Color(0, 0, 0, 0).rgb
|
||||
val rgb = colorPalette[colorIdx.toInt()].rgb
|
||||
for (c in normalChars) {
|
||||
val colored = c.copy()
|
||||
for (y in 0 until colored.height)
|
||||
for (x in 0 until colored.width) {
|
||||
if (colored.getRGB(x, y) != transparent) {
|
||||
colored.setRGB(x, y, rgb)
|
||||
}
|
||||
}
|
||||
coloredChars.add(colored)
|
||||
}
|
||||
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
|
||||
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun BufferedImage.copy(): BufferedImage {
|
||||
val bcopy = BufferedImage(this.width, this.height, this.type)
|
||||
val g = bcopy.graphics
|
||||
g.drawImage(this, 0, 0, null)
|
||||
g.dispose()
|
||||
return bcopy
|
||||
}
|
||||
|
||||
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
|
||||
Color(0x000000), // 0 = black
|
||||
Color(0xFFFFFF), // 1 = white
|
||||
Color(0x813338), // 2 = red
|
||||
Color(0x75cec8), // 3 = cyan
|
||||
Color(0x8e3c97), // 4 = purple
|
||||
Color(0x56ac4d), // 5 = green
|
||||
Color(0x2e2c9b), // 6 = blue
|
||||
Color(0xedf171), // 7 = yellow
|
||||
Color(0x8e5029), // 8 = orange
|
||||
Color(0x553800), // 9 = brown
|
||||
Color(0xc46c71), // 10 = light red
|
||||
Color(0x4a4a4a), // 11 = dark grey
|
||||
Color(0x7b7b7b), // 12 = medium grey
|
||||
Color(0xa9ff9f), // 13 = light green
|
||||
Color(0x706deb), // 14 = light blue
|
||||
Color(0xb2b2b2) // 15 = light grey
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.antlr.escape
|
||||
@ -7,17 +8,20 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.IAssemblyGenerator
|
||||
import prog8.compiler.target.IAssemblyProgram
|
||||
import prog8.compiler.target.c64.AssemblyProgram
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
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.AssignmentAsmGen
|
||||
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
|
||||
import prog8.compiler.target.generatedLabelPrefix
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.functions.FSignature
|
||||
import java.math.RoundingMode
|
||||
import java.nio.file.Path
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
@ -26,8 +30,9 @@ import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
internal class AsmGen(private val program: Program,
|
||||
private val zeropage: Zeropage,
|
||||
private val options: CompilationOptions,
|
||||
val errors: ErrorReporter,
|
||||
val zeropage: Zeropage,
|
||||
val options: CompilationOptions,
|
||||
private val outputDir: Path): IAssemblyGenerator {
|
||||
|
||||
private val assemblyLines = mutableListOf<String>()
|
||||
@ -41,13 +46,11 @@ internal class AsmGen(private val program: Program,
|
||||
private val assignmentAsmGen = AssignmentAsmGen(program, this)
|
||||
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
|
||||
internal val loopEndLabels = ArrayDeque<String>()
|
||||
internal val loopContinueLabels = ArrayDeque<String>()
|
||||
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
|
||||
|
||||
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
|
||||
assemblyLines.clear()
|
||||
loopEndLabels.clear()
|
||||
loopContinueLabels.clear()
|
||||
|
||||
println("Generating assembly code... ")
|
||||
|
||||
@ -76,16 +79,33 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
private fun header() {
|
||||
val ourName = this.javaClass.name
|
||||
out("; 6502 assembly code for '${program.name}'")
|
||||
val cpu = when(CompilationTarget.machine.cpu) {
|
||||
CpuType.CPU6502 -> "6502"
|
||||
CpuType.CPU65c02 -> "65c02"
|
||||
else -> "unsupported"
|
||||
}
|
||||
|
||||
out("; $cpu assembly code for '${program.name}'")
|
||||
out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}")
|
||||
out("; assembler syntax is for the 64tasm cross-assembler")
|
||||
out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
|
||||
out("\n.cpu '6502'\n.enc 'none'\n")
|
||||
out("\n.cpu '$cpu'\n.enc 'none'\n")
|
||||
|
||||
program.actualLoadAddress = program.definedLoadAddress
|
||||
if (program.actualLoadAddress == 0) // fix load address
|
||||
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
|
||||
C64MachineDefinition.BASIC_LOAD_ADDRESS else C64MachineDefinition.RAW_LOAD_ADDRESS
|
||||
CompilationTarget.machine.BASIC_LOAD_ADDRESS else CompilationTarget.machine.RAW_LOAD_ADDRESS
|
||||
|
||||
// the global prog8 variables needed
|
||||
val zp = CompilationTarget.machine.zeropage
|
||||
val initproc = CompilationTarget.machine.initSystemProcname
|
||||
out("P8ZP_SCRATCH_B1 = ${zp.SCRATCH_B1}")
|
||||
out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
|
||||
out("P8ZP_SCRATCH_REG_X = ${zp.SCRATCH_REG_X}")
|
||||
out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
|
||||
out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
|
||||
out("P8ESTACK_LO = ${CompilationTarget.machine.ESTACK_LO.toHex()}")
|
||||
out("P8ESTACK_HI = ${CompilationTarget.machine.ESTACK_HI.toHex()}")
|
||||
|
||||
when {
|
||||
options.launcher == LauncherType.BASIC -> {
|
||||
@ -100,14 +120,16 @@ internal class AsmGen(private val program: Program,
|
||||
out("_prog8_entrypoint\t; assembly code starts here\n")
|
||||
out(" tsx")
|
||||
out(" stx prog8_lib.orig_stackpointer")
|
||||
out(" jsr prog8_lib.init_system")
|
||||
if(!initproc.isNullOrEmpty())
|
||||
out(" jsr $initproc")
|
||||
}
|
||||
options.output == OutputType.PRG -> {
|
||||
out("; ---- program without basic sys call ----")
|
||||
out("* = ${program.actualLoadAddress.toHex()}\n")
|
||||
out(" tsx")
|
||||
out(" stx prog8_lib.orig_stackpointer")
|
||||
out(" jsr prog8_lib.init_system")
|
||||
if(!initproc.isNullOrEmpty())
|
||||
out(" jsr $initproc")
|
||||
}
|
||||
options.output == OutputType.RAW -> {
|
||||
out("; ---- raw assembler program ----")
|
||||
@ -148,8 +170,7 @@ internal class AsmGen(private val program: Program,
|
||||
// the global list of all floating point constants for the whole program
|
||||
out("; global float constants")
|
||||
for (flt in globalFloatConsts) {
|
||||
val mflpt5 = C64MachineDefinition.Mflpt5.fromNumber(flt.key)
|
||||
val floatFill = makeFloatFill(mflpt5)
|
||||
val floatFill = CompilationTarget.machine.getFloat(flt.key).makeFloatFillAsm()
|
||||
val floatvalue = flt.key
|
||||
out("${flt.value}\t.byte $floatFill ; float $floatvalue")
|
||||
}
|
||||
@ -182,10 +203,7 @@ internal class AsmGen(private val program: Program,
|
||||
blockLevelVarInits.getValue(block).forEach { decl ->
|
||||
val scopedFullName = decl.makeScopedName(decl.name).split('.')
|
||||
require(scopedFullName.first()==block.name)
|
||||
val target = AssignTarget(null, IdentifierReference(scopedFullName.drop(1), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, null, decl.value!!, decl.position)
|
||||
assign.linkParents(decl.parent)
|
||||
assignmentAsmGen.translate(assign)
|
||||
assignInitialValueToVar(decl, scopedFullName.drop(1))
|
||||
}
|
||||
out(" rts\n .pend")
|
||||
}
|
||||
@ -193,6 +211,16 @@ internal class AsmGen(private val program: Program,
|
||||
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
|
||||
}
|
||||
|
||||
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
|
||||
val variable = IdentifierReference(variableName, decl.position)
|
||||
variable.linkParents(decl.parent)
|
||||
val asgn = AsmAssignment(
|
||||
AsmAssignSource.fromAstSource(decl.value!!, program),
|
||||
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, variable = variable),
|
||||
false, decl.position)
|
||||
assignmentAsmGen.translateNormalAssignment(asgn)
|
||||
}
|
||||
|
||||
private var generatedLabelSequenceNumber: Int = 0
|
||||
|
||||
internal fun makeLabel(postfix: String): String {
|
||||
@ -216,15 +244,6 @@ internal class AsmGen(private val program: Program,
|
||||
} else assemblyLines.add(fragment)
|
||||
}
|
||||
|
||||
private fun makeFloatFill(flt: C64MachineDefinition.Mflpt5): String {
|
||||
val b0 = "$" + flt.b0.toString(16).padStart(2, '0')
|
||||
val b1 = "$" + flt.b1.toString(16).padStart(2, '0')
|
||||
val b2 = "$" + flt.b2.toString(16).padStart(2, '0')
|
||||
val b3 = "$" + flt.b3.toString(16).padStart(2, '0')
|
||||
val b4 = "$" + flt.b4.toString(16).padStart(2, '0')
|
||||
return "$b0, $b1, $b2, $b3, $b4"
|
||||
}
|
||||
|
||||
private fun encode(str: String, altEncoding: Boolean): List<Short> {
|
||||
val bytes = if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
|
||||
return bytes.plus(0)
|
||||
@ -234,7 +253,6 @@ internal class AsmGen(private val program: Program,
|
||||
out("; vars allocated on zeropage")
|
||||
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
|
||||
for(variable in variables) {
|
||||
// should NOT allocate subroutine parameters on the zero page
|
||||
val fullName = variable.makeScopedName(variable.name)
|
||||
val zpVar = allocatedZeropageVariables[fullName]
|
||||
if(zpVar==null) {
|
||||
@ -259,12 +277,13 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
|
||||
private fun vardecl2asm(decl: VarDecl) {
|
||||
val name = decl.name
|
||||
when (decl.datatype) {
|
||||
DataType.UBYTE -> out("${decl.name}\t.byte 0")
|
||||
DataType.BYTE -> out("${decl.name}\t.char 0")
|
||||
DataType.UWORD -> out("${decl.name}\t.word 0")
|
||||
DataType.WORD -> out("${decl.name}\t.sint 0")
|
||||
DataType.FLOAT -> out("${decl.name}\t.byte 0,0,0,0,0 ; float")
|
||||
DataType.UBYTE -> out("$name\t.byte 0")
|
||||
DataType.BYTE -> out("$name\t.char 0")
|
||||
DataType.UWORD -> out("$name\t.word 0")
|
||||
DataType.WORD -> out("$name\t.sint 0")
|
||||
DataType.FLOAT -> out("$name\t.byte 0,0,0,0,0 ; float")
|
||||
DataType.STRUCT -> {} // is flattened
|
||||
DataType.STR -> {
|
||||
val str = decl.value as StringLiteralValue
|
||||
@ -273,9 +292,9 @@ internal class AsmGen(private val program: Program,
|
||||
DataType.ARRAY_UB -> {
|
||||
val data = makeArrayFillDataUnsigned(decl)
|
||||
if (data.size <= 16)
|
||||
out("${decl.name}\t.byte ${data.joinToString()}")
|
||||
out("$name\t.byte ${data.joinToString()}")
|
||||
else {
|
||||
out(decl.name)
|
||||
out(name)
|
||||
for (chunk in data.chunked(16))
|
||||
out(" .byte " + chunk.joinToString())
|
||||
}
|
||||
@ -283,9 +302,9 @@ internal class AsmGen(private val program: Program,
|
||||
DataType.ARRAY_B -> {
|
||||
val data = makeArrayFillDataSigned(decl)
|
||||
if (data.size <= 16)
|
||||
out("${decl.name}\t.char ${data.joinToString()}")
|
||||
out("$name\t.char ${data.joinToString()}")
|
||||
else {
|
||||
out(decl.name)
|
||||
out(name)
|
||||
for (chunk in data.chunked(16))
|
||||
out(" .char " + chunk.joinToString())
|
||||
}
|
||||
@ -293,9 +312,9 @@ internal class AsmGen(private val program: Program,
|
||||
DataType.ARRAY_UW -> {
|
||||
val data = makeArrayFillDataUnsigned(decl)
|
||||
if (data.size <= 16)
|
||||
out("${decl.name}\t.word ${data.joinToString()}")
|
||||
out("$name\t.word ${data.joinToString()}")
|
||||
else {
|
||||
out(decl.name)
|
||||
out(name)
|
||||
for (chunk in data.chunked(16))
|
||||
out(" .word " + chunk.joinToString())
|
||||
}
|
||||
@ -303,9 +322,9 @@ internal class AsmGen(private val program: Program,
|
||||
DataType.ARRAY_W -> {
|
||||
val data = makeArrayFillDataSigned(decl)
|
||||
if (data.size <= 16)
|
||||
out("${decl.name}\t.sint ${data.joinToString()}")
|
||||
out("$name\t.sint ${data.joinToString()}")
|
||||
else {
|
||||
out(decl.name)
|
||||
out(name)
|
||||
for (chunk in data.chunked(16))
|
||||
out(" .sint " + chunk.joinToString())
|
||||
}
|
||||
@ -317,13 +336,13 @@ internal class AsmGen(private val program: Program,
|
||||
else {
|
||||
// no init value, use zeros
|
||||
val zero = decl.zeroElementValue()
|
||||
Array(decl.arraysize!!.size()!!) { zero }
|
||||
Array(decl.arraysize!!.constIndex()!!) { zero }
|
||||
}
|
||||
val floatFills = array.map {
|
||||
val number = (it as NumericLiteralValue).number
|
||||
makeFloatFill(C64MachineDefinition.Mflpt5.fromNumber(number))
|
||||
CompilationTarget.machine.getFloat(number).makeFloatFillAsm()
|
||||
}
|
||||
out(decl.name)
|
||||
out(name)
|
||||
for (f in array.zip(floatFills))
|
||||
out(" .byte ${f.second} ; float ${f.first}")
|
||||
}
|
||||
@ -392,7 +411,7 @@ internal class AsmGen(private val program: Program,
|
||||
else {
|
||||
// no array init value specified, use a list of zeros
|
||||
val zero = decl.zeroElementValue()
|
||||
Array(decl.arraysize!!.size()!!) { zero }
|
||||
Array(decl.arraysize!!.constIndex()!!) { zero }
|
||||
}
|
||||
return when (decl.datatype) {
|
||||
DataType.ARRAY_UB ->
|
||||
@ -419,7 +438,7 @@ internal class AsmGen(private val program: Program,
|
||||
else {
|
||||
// no array init value specified, use a list of zeros
|
||||
val zero = decl.zeroElementValue()
|
||||
Array(decl.arraysize!!.size()!!) { zero }
|
||||
Array(decl.arraysize!!.constIndex()!!) { zero }
|
||||
}
|
||||
return when (decl.datatype) {
|
||||
DataType.ARRAY_UB ->
|
||||
@ -454,150 +473,104 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getFloatConst(number: Double): String {
|
||||
// try to match the ROM float constants to save memory
|
||||
val mflpt5 = C64MachineDefinition.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.FL_ZERO"
|
||||
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"
|
||||
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"
|
||||
else -> {}
|
||||
}
|
||||
|
||||
// no ROM float const for this value, create our own
|
||||
val name = globalFloatConsts[number]
|
||||
if(name!=null)
|
||||
return name
|
||||
val newName = "prog8_float_const_${globalFloatConsts.size}"
|
||||
globalFloatConsts[number] = newName
|
||||
return newName
|
||||
internal fun getFloatAsmConst(number: Double): String {
|
||||
var asmName = CompilationTarget.machine.getFloatRomConst(number)
|
||||
if(asmName.isNullOrEmpty()) {
|
||||
// no ROM float const for this value, create our own
|
||||
asmName = globalFloatConsts[number]
|
||||
if(asmName==null) {
|
||||
asmName = "prog8_float_const_${globalFloatConsts.size}"
|
||||
globalFloatConsts[number] = asmName
|
||||
}
|
||||
}
|
||||
return asmName
|
||||
}
|
||||
|
||||
internal fun asmSymbolName(identifier: IdentifierReference): String {
|
||||
return if(identifier.memberOfStruct(program.namespace)!=null) {
|
||||
val name = identifier.targetVarDecl(program.namespace)!!.name
|
||||
fixNameSymbols(name)
|
||||
} else {
|
||||
fixNameSymbols(identifier.nameInSource.joinToString("."))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun signExtendAtoMsb(destination: String) =
|
||||
"""
|
||||
ora #$7f
|
||||
bmi +
|
||||
lda #0
|
||||
+ sta $destination
|
||||
"""
|
||||
|
||||
internal fun asmIdentifierName(identifier: IdentifierReference): String {
|
||||
val name = if(identifier.memberOfStruct(program.namespace)!=null) {
|
||||
identifier.targetVarDecl(program.namespace)!!.name
|
||||
internal fun asmVariableName(identifier: IdentifierReference): String {
|
||||
return if(identifier.memberOfStruct(program.namespace)!=null) {
|
||||
val name = identifier.targetVarDecl(program.namespace)!!.name
|
||||
fixNameSymbols(name)
|
||||
} else {
|
||||
identifier.nameInSource.joinToString(".")
|
||||
fixNameSymbols(identifier.nameInSource.joinToString("."))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
|
||||
// returns if the pointer is already on the ZP itself or not (in which case SCRATCH_W1 is used as intermediary)
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program.namespace)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
return if(scopedName in allocatedZeropageVariables) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
out(" ldy #0 | lda ($sourceName),y")
|
||||
Pair(true, sourceName)
|
||||
} else {
|
||||
out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W1),y""")
|
||||
Pair(false, sourceName)
|
||||
}
|
||||
}
|
||||
|
||||
fun storeByteIntoPointer(pointervar: IdentifierReference, ldaInstructionArg: String?) {
|
||||
val sourceName = asmVariableName(pointervar)
|
||||
val vardecl = pointervar.targetVarDecl(program.namespace)!!
|
||||
val scopedName = vardecl.makeScopedName(vardecl.name)
|
||||
if(scopedName in allocatedZeropageVariables) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
if(ldaInstructionArg!=null)
|
||||
out(" lda $ldaInstructionArg")
|
||||
out(" ldy #0 | sta ($sourceName),y")
|
||||
} else {
|
||||
out("""
|
||||
ldy $sourceName
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy $sourceName+1
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
${if(ldaInstructionArg==null) "" else "lda $ldaInstructionArg"}
|
||||
ldy #0
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
}
|
||||
return fixNameSymbols(name)
|
||||
}
|
||||
|
||||
internal fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
|
||||
|
||||
private fun branchInstruction(condition: BranchCondition, complement: Boolean) =
|
||||
if(complement) {
|
||||
when (condition) {
|
||||
BranchCondition.CS -> "bcc"
|
||||
BranchCondition.CC -> "bcs"
|
||||
BranchCondition.EQ, BranchCondition.Z -> "beq"
|
||||
BranchCondition.NE, BranchCondition.NZ -> "bne"
|
||||
BranchCondition.VS -> "bvc"
|
||||
BranchCondition.VC -> "bvs"
|
||||
BranchCondition.MI, BranchCondition.NEG -> "bmi"
|
||||
BranchCondition.PL, BranchCondition.POS -> "bpl"
|
||||
}
|
||||
} else {
|
||||
when (condition) {
|
||||
BranchCondition.CS -> "bcs"
|
||||
BranchCondition.CC -> "bcc"
|
||||
BranchCondition.EQ, BranchCondition.Z -> "beq"
|
||||
BranchCondition.NE, BranchCondition.NZ -> "bne"
|
||||
BranchCondition.VS -> "bvs"
|
||||
BranchCondition.VC -> "bvc"
|
||||
BranchCondition.MI, BranchCondition.NEG -> "bmi"
|
||||
BranchCondition.PL, BranchCondition.POS -> "bpl"
|
||||
}
|
||||
internal fun saveRegister(register: CpuRegister) {
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" pha")
|
||||
CpuRegister.X -> {
|
||||
if(CompilationTarget.machine.cpu == CpuType.CPU65c02)
|
||||
out(" phx")
|
||||
else
|
||||
out(" stx P8ZP_SCRATCH_REG_X")
|
||||
}
|
||||
|
||||
internal fun readAndPushArrayvalueWithIndexA(arrayDt: DataType, variable: IdentifierReference) {
|
||||
val variablename = asmIdentifierName(variable)
|
||||
when (arrayDt) {
|
||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B ->
|
||||
out(" tay | lda $variablename,y | sta $ESTACK_LO_HEX,x | dex")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W ->
|
||||
out(" asl a | tay | lda $variablename,y | sta $ESTACK_LO_HEX,x | lda $variablename+1,y | sta $ESTACK_HI_HEX,x | dex")
|
||||
DataType.ARRAY_F ->
|
||||
// index * 5 is done in the subroutine that's called
|
||||
out("""
|
||||
sta $ESTACK_LO_HEX,x
|
||||
dex
|
||||
lda #<$variablename
|
||||
ldy #>$variablename
|
||||
jsr c64flt.push_float_from_indexed_var
|
||||
""")
|
||||
else ->
|
||||
throw AssemblyError("weird array type")
|
||||
CpuRegister.Y -> out(" tya | pha")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun saveRegister(register: Register) {
|
||||
internal fun restoreRegister(register: CpuRegister) {
|
||||
when(register) {
|
||||
Register.A -> out(" pha")
|
||||
Register.X -> out(" txa | pha")
|
||||
Register.Y -> out(" tya | pha")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreRegister(register: Register) {
|
||||
when(register) {
|
||||
Register.A -> out(" pla")
|
||||
Register.X -> out(" pla | tax")
|
||||
Register.Y -> out(" pla | tay")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateSubroutine(sub: Subroutine) {
|
||||
out("")
|
||||
outputSourceLine(sub)
|
||||
|
||||
if(sub.isAsmSubroutine) {
|
||||
if(sub.asmAddress!=null)
|
||||
return // already done at the memvars section
|
||||
|
||||
// asmsub with most likely just an inline asm in it
|
||||
out("${sub.name}\t.proc")
|
||||
sub.statements.forEach{ translate(it) }
|
||||
out(" .pend\n")
|
||||
} else {
|
||||
// regular subroutine
|
||||
out("${sub.name}\t.proc")
|
||||
zeropagevars2asm(sub.statements)
|
||||
memdefs2asm(sub.statements)
|
||||
out("; statements")
|
||||
sub.statements.forEach{ translate(it) }
|
||||
out("; variables")
|
||||
vardecls2asm(sub.statements)
|
||||
out(" .pend\n")
|
||||
CpuRegister.A -> out(" pla")
|
||||
CpuRegister.X -> {
|
||||
if(CompilationTarget.machine.cpu == CpuType.CPU65c02)
|
||||
out(" plx")
|
||||
else
|
||||
out(" ldx P8ZP_SCRATCH_REG_X")
|
||||
}
|
||||
CpuRegister.Y -> out(" pla | tay")
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,11 +609,14 @@ internal class AsmGen(private val program: Program,
|
||||
is BranchStatement -> translate(stmt)
|
||||
is IfStatement -> translate(stmt)
|
||||
is ForLoop -> forloopsAsmGen.translate(stmt)
|
||||
is Continue -> out(" jmp ${loopContinueLabels.peek()}")
|
||||
is Break -> out(" jmp ${loopEndLabels.peek()}")
|
||||
is Break -> {
|
||||
if(loopEndLabels.isEmpty())
|
||||
throw AssemblyError("break statement out of context ${stmt.position}")
|
||||
out(" jmp ${loopEndLabels.peek()}")
|
||||
}
|
||||
is WhileLoop -> translate(stmt)
|
||||
is ForeverLoop -> translate(stmt)
|
||||
is RepeatLoop -> translate(stmt)
|
||||
is UntilLoop -> translate(stmt)
|
||||
is WhenStatement -> translate(stmt)
|
||||
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
|
||||
is AnonymousScope -> translate(stmt)
|
||||
@ -649,6 +625,172 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
internal fun loadScaledArrayIndexIntoRegister(expr: ArrayIndexedExpression,
|
||||
elementDt: DataType,
|
||||
register: CpuRegister,
|
||||
addOneExtra: Boolean=false) {
|
||||
val reg = register.toString().toLowerCase()
|
||||
val index = expr.arrayspec.index
|
||||
if(index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize() + if(addOneExtra) 1 else 0
|
||||
out(" ld$reg #$indexValue")
|
||||
return
|
||||
}
|
||||
|
||||
if(addOneExtra) {
|
||||
// add 1 to the result
|
||||
if (index is IdentifierReference) {
|
||||
val indexName = asmVariableName(index)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
out(" ldy $indexName | iny")
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" tya")
|
||||
CpuRegister.X -> out(" tyx")
|
||||
CpuRegister.Y -> {}
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | sec | rol a")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
sec
|
||||
adc $indexName""")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
out("""
|
||||
inc P8ESTACK_LO,x
|
||||
bne +
|
||||
inc P8ESTACK_HI,x
|
||||
+""")
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
|
||||
CpuRegister.X -> throw AssemblyError("can't use X here")
|
||||
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (index is IdentifierReference) {
|
||||
val indexName = asmVariableName(index)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> out(" ld$reg $indexName")
|
||||
in WordDatatypes -> {
|
||||
out(" lda $indexName | asl a")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
require(DataType.FLOAT.memorySize()==5)
|
||||
out("""
|
||||
lda $indexName
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc $indexName""")
|
||||
when(register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> out(" tax")
|
||||
CpuRegister.Y -> out(" tay")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
when(register) {
|
||||
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
|
||||
CpuRegister.X -> throw AssemblyError("can't use X here")
|
||||
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateExpression(expression: Expression) =
|
||||
expressionsAsmGen.translateExpression(expression)
|
||||
|
||||
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
|
||||
|
||||
internal fun translateFunctionCall(functionCall: FunctionCall) =
|
||||
functioncallAsmGen.translateFunctionCall(functionCall)
|
||||
|
||||
internal fun translateNormalAssignment(assign: AsmAssignment) =
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
|
||||
private fun translateSubroutine(sub: Subroutine) {
|
||||
out("")
|
||||
outputSourceLine(sub)
|
||||
|
||||
if(sub.isAsmSubroutine) {
|
||||
if(sub.asmAddress!=null)
|
||||
return // already done at the memvars section
|
||||
|
||||
// asmsub with most likely just an inline asm in it
|
||||
out("${sub.name}\t.proc")
|
||||
sub.statements.forEach{ translate(it) }
|
||||
out(" .pend\n")
|
||||
} else {
|
||||
// regular subroutine
|
||||
out("${sub.name}\t.proc")
|
||||
zeropagevars2asm(sub.statements)
|
||||
memdefs2asm(sub.statements)
|
||||
out("; statements")
|
||||
sub.statements.forEach{ translate(it) }
|
||||
out("; variables")
|
||||
vardecls2asm(sub.statements)
|
||||
out(" .pend\n")
|
||||
}
|
||||
}
|
||||
|
||||
private fun branchInstruction(condition: BranchCondition, complement: Boolean) =
|
||||
if(complement) {
|
||||
when (condition) {
|
||||
BranchCondition.CS -> "bcc"
|
||||
BranchCondition.CC -> "bcs"
|
||||
BranchCondition.EQ, BranchCondition.Z -> "beq"
|
||||
BranchCondition.NE, BranchCondition.NZ -> "bne"
|
||||
BranchCondition.VS -> "bvc"
|
||||
BranchCondition.VC -> "bvs"
|
||||
BranchCondition.MI, BranchCondition.NEG -> "bmi"
|
||||
BranchCondition.PL, BranchCondition.POS -> "bpl"
|
||||
}
|
||||
} else {
|
||||
when (condition) {
|
||||
BranchCondition.CS -> "bcs"
|
||||
BranchCondition.CC -> "bcc"
|
||||
BranchCondition.EQ, BranchCondition.Z -> "beq"
|
||||
BranchCondition.NE, BranchCondition.NZ -> "bne"
|
||||
BranchCondition.VS -> "bvs"
|
||||
BranchCondition.VC -> "bvc"
|
||||
BranchCondition.MI, BranchCondition.NEG -> "bmi"
|
||||
BranchCondition.PL, BranchCondition.POS -> "bpl"
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: IfStatement) {
|
||||
expressionsAsmGen.translateExpression(stmt.condition)
|
||||
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
|
||||
@ -664,45 +806,145 @@ internal class AsmGen(private val program: Program,
|
||||
|
||||
private fun translateTestStack(dataType: DataType) {
|
||||
when(dataType) {
|
||||
in ByteDatatypes -> out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
in WordDatatypes -> out(" inx | lda $ESTACK_LO_HEX,x | ora $ESTACK_HI_HEX,x")
|
||||
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 translate(stmt: ForeverLoop) {
|
||||
val foreverLabel = makeLabel("forever")
|
||||
val endLabel = makeLabel("foreverend")
|
||||
private fun translate(stmt: RepeatLoop) {
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val endLabel = makeLabel("repeatend")
|
||||
loopEndLabels.push(endLabel)
|
||||
loopContinueLabels.push(foreverLabel)
|
||||
out(foreverLabel)
|
||||
translate(stmt.body)
|
||||
out(" jmp $foreverLabel")
|
||||
out(endLabel)
|
||||
|
||||
when (stmt.iterations) {
|
||||
null -> {
|
||||
// endless loop
|
||||
out(repeatLabel)
|
||||
translate(stmt.body)
|
||||
out(" jmp $repeatLabel")
|
||||
out(endLabel)
|
||||
}
|
||||
is NumericLiteralValue -> {
|
||||
val iterations = (stmt.iterations as NumericLiteralValue).number.toInt()
|
||||
if(iterations<0 || iterations > 65536)
|
||||
throw AssemblyError("invalid number of iterations")
|
||||
when {
|
||||
iterations == 0 -> {}
|
||||
iterations <= 256 -> {
|
||||
out(" lda #${iterations and 255}")
|
||||
repeatByteCountInA(iterations, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
else -> {
|
||||
out(" lda #<${iterations} | ldy #>${iterations}")
|
||||
repeatWordCountInAY(iterations, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val vardecl = (stmt.iterations as IdentifierReference).targetStatement(program.namespace) as VarDecl
|
||||
val name = asmVariableName(stmt.iterations as IdentifierReference)
|
||||
when(vardecl.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
out(" lda $name")
|
||||
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
out(" lda $name | ldy $name+1")
|
||||
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
translateExpression(stmt.iterations!!)
|
||||
val dt = stmt.iterations!!.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when (dt) {
|
||||
in ByteDatatypes -> {
|
||||
out(" inx | lda P8ESTACK_LO,x")
|
||||
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
out(" inx | lda P8ESTACK_LO,x | ldy P8ESTACK_HI,x")
|
||||
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop expression datatype $dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loopEndLabels.pop()
|
||||
loopContinueLabels.pop()
|
||||
}
|
||||
|
||||
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
// note: A/Y must have been loaded with the number of iterations already!
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
out("""
|
||||
sta $counterVar
|
||||
sty $counterVar+1
|
||||
$repeatLabel lda $counterVar
|
||||
bne +
|
||||
lda $counterVar+1
|
||||
beq $endLabel
|
||||
+ lda $counterVar
|
||||
bne +
|
||||
dec $counterVar+1
|
||||
+ dec $counterVar
|
||||
""")
|
||||
translate(body)
|
||||
out(" jmp $repeatLabel")
|
||||
if(constIterations!=null && constIterations>=16 && zeropage.available() > 1) {
|
||||
// allocate count var on ZP
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, body.position, errors)
|
||||
out("""$counterVar = $zpAddr ; auto zp UWORD""")
|
||||
} else {
|
||||
out("""
|
||||
$counterVar .word 0""")
|
||||
}
|
||||
out(endLabel)
|
||||
|
||||
}
|
||||
|
||||
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
|
||||
// note: A must have been loaded with the number of iterations already!
|
||||
val counterVar = makeLabel("repeatcounter")
|
||||
out("""
|
||||
sta $counterVar
|
||||
$repeatLabel""")
|
||||
translate(body)
|
||||
out("""
|
||||
dec $counterVar
|
||||
bne $repeatLabel
|
||||
beq $endLabel""")
|
||||
if(constIterations!=null && constIterations>=16 && zeropage.available() > 0) {
|
||||
// allocate count var on ZP
|
||||
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, body.position, errors)
|
||||
out("""$counterVar = $zpAddr ; auto zp UBYTE""")
|
||||
} else {
|
||||
out("""
|
||||
$counterVar .byte 0""")
|
||||
}
|
||||
out(endLabel)
|
||||
}
|
||||
|
||||
private fun translate(stmt: WhileLoop) {
|
||||
val whileLabel = makeLabel("while")
|
||||
val endLabel = makeLabel("whileend")
|
||||
loopEndLabels.push(endLabel)
|
||||
loopContinueLabels.push(whileLabel)
|
||||
out(whileLabel)
|
||||
// TODO optimize for the simple cases, can we avoid stack use?
|
||||
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 $ESTACK_LO_HEX,x | beq $endLabel")
|
||||
out(" inx | lda P8ESTACK_LO,x | beq $endLabel")
|
||||
} else {
|
||||
out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
lda P8ESTACK_LO,x
|
||||
bne +
|
||||
lda $ESTACK_HI_HEX,x
|
||||
lda P8ESTACK_HI,x
|
||||
beq $endLabel
|
||||
+ """)
|
||||
}
|
||||
@ -710,35 +952,31 @@ internal class AsmGen(private val program: Program,
|
||||
out(" jmp $whileLabel")
|
||||
out(endLabel)
|
||||
loopEndLabels.pop()
|
||||
loopContinueLabels.pop()
|
||||
}
|
||||
|
||||
private fun translate(stmt: RepeatLoop) {
|
||||
private fun translate(stmt: UntilLoop) {
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val endLabel = makeLabel("repeatend")
|
||||
loopEndLabels.push(endLabel)
|
||||
loopContinueLabels.push(repeatLabel)
|
||||
out(repeatLabel)
|
||||
// TODO optimize this for the simple cases, can we avoid stack use?
|
||||
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 $ESTACK_LO_HEX,x | beq $repeatLabel")
|
||||
out(" inx | lda P8ESTACK_LO,x | beq $repeatLabel")
|
||||
} else {
|
||||
out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
lda P8ESTACK_LO,x
|
||||
bne +
|
||||
lda $ESTACK_HI_HEX,x
|
||||
lda P8ESTACK_HI,x
|
||||
beq $repeatLabel
|
||||
+ """)
|
||||
}
|
||||
out(endLabel)
|
||||
loopEndLabels.pop()
|
||||
loopContinueLabels.pop()
|
||||
}
|
||||
|
||||
private fun translate(stmt: WhenStatement) {
|
||||
@ -749,9 +987,9 @@ internal class AsmGen(private val program: Program,
|
||||
if(!conditionDt.isKnown)
|
||||
throw AssemblyError("unknown condition dt")
|
||||
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes)
|
||||
out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
out(" inx | lda P8ESTACK_LO,x")
|
||||
else
|
||||
out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
|
||||
out(" inx | lda P8ESTACK_LO,x | ldy P8ESTACK_HI,x")
|
||||
for(choice in stmt.choices) {
|
||||
val choiceLabel = makeLabel("choice")
|
||||
if(choice.values==null) {
|
||||
@ -770,7 +1008,7 @@ internal class AsmGen(private val program: Program,
|
||||
bne +
|
||||
cpy #>${value.toHex()}
|
||||
beq $choiceLabel
|
||||
+
|
||||
+
|
||||
""")
|
||||
}
|
||||
}
|
||||
@ -837,10 +1075,10 @@ internal class AsmGen(private val program: Program,
|
||||
}
|
||||
inits.add(stmt)
|
||||
} else {
|
||||
val target = AssignTarget(null, IdentifierReference(listOf(stmt.name), stmt.position), null, null, stmt.position)
|
||||
val assign = Assignment(target, null, stmt.value!!, stmt.position)
|
||||
assign.linkParents(stmt.parent)
|
||||
translate(assign)
|
||||
val next = (stmt.parent as INameScope).nextSibling(stmt)
|
||||
if (next !is ForLoop || next.loopVar.nameInSource.single() != stmt.name) {
|
||||
assignInitialValueToVar(stmt, listOf(stmt.name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -877,7 +1115,7 @@ internal class AsmGen(private val program: Program,
|
||||
return when {
|
||||
jmp.identifier!=null -> {
|
||||
val target = jmp.identifier.targetStatement(program.namespace)
|
||||
val asmName = asmIdentifierName(jmp.identifier)
|
||||
val asmName = asmSymbolName(jmp.identifier)
|
||||
if(target is Label)
|
||||
"_$asmName" // prefix with underscore to jump to local label
|
||||
else
|
||||
@ -898,61 +1136,4 @@ internal class AsmGen(private val program: Program,
|
||||
val assembly = asm.assembly.trimEnd().trimStart('\n')
|
||||
assemblyLines.add(assembly)
|
||||
}
|
||||
|
||||
internal fun translateArrayIndexIntoA(expr: ArrayIndexedExpression) {
|
||||
when (val index = expr.arrayspec.index) {
|
||||
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
|
||||
is RegisterExpr -> {
|
||||
when (index.register) {
|
||||
Register.A -> {}
|
||||
Register.X -> out(" txa")
|
||||
Register.Y -> out(" tya")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val indexName = asmIdentifierName(index)
|
||||
out(" lda $indexName")
|
||||
}
|
||||
else -> {
|
||||
expressionsAsmGen.translateExpression(index)
|
||||
out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun translateExpression(expression: Expression) =
|
||||
expressionsAsmGen.translateExpression(expression)
|
||||
|
||||
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
|
||||
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
|
||||
|
||||
internal fun translateFunctionCall(functionCall: FunctionCall) =
|
||||
functioncallAsmGen.translateFunctionCall(functionCall)
|
||||
|
||||
internal fun assignFromEvalResult(target: AssignTarget) =
|
||||
assignmentAsmGen.assignFromEvalResult(target)
|
||||
|
||||
fun assignFromByteConstant(target: AssignTarget, value: Short) =
|
||||
assignmentAsmGen.assignFromByteConstant(target, value)
|
||||
|
||||
fun assignFromWordConstant(target: AssignTarget, value: Int) =
|
||||
assignmentAsmGen.assignFromWordConstant(target, value)
|
||||
|
||||
fun assignFromFloatConstant(target: AssignTarget, value: Double) =
|
||||
assignmentAsmGen.assignFromFloatConstant(target, value)
|
||||
|
||||
fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) =
|
||||
assignmentAsmGen.assignFromByteVariable(target, variable)
|
||||
|
||||
fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) =
|
||||
assignmentAsmGen.assignFromWordVariable(target, variable)
|
||||
|
||||
fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) =
|
||||
assignmentAsmGen.assignFromFloatVariable(target, variable)
|
||||
|
||||
fun assignFromRegister(target: AssignTarget, register: Register) =
|
||||
assignmentAsmGen.assignFromRegister(target, register)
|
||||
|
||||
fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) =
|
||||
assignmentAsmGen.assignFromMemoryByte(target, address, identifier)
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||
|
||||
|
||||
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
|
||||
|
||||
@ -87,10 +84,10 @@ private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>):
|
||||
// the repeated lda can be removed
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
|
||||
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
|
||||
lines[1].value.trim().startsWith("cmp ") &&
|
||||
lines[2].value.trim().startsWith("beq ") &&
|
||||
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
|
||||
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
|
||||
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
|
||||
}
|
||||
}
|
||||
@ -102,10 +99,10 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
|
||||
// this is a lot harder for word values because the instruction sequence varies.
|
||||
val mods = mutableListOf<Modification>()
|
||||
for(lines in linesByFour) {
|
||||
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
|
||||
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
|
||||
lines[1].value.trim()=="dex" &&
|
||||
lines[2].value.trim()=="inx" &&
|
||||
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
|
||||
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
|
||||
mods.add(Modification(lines[1].index, true, null))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
mods.add(Modification(lines[3].index, true, null))
|
||||
@ -154,7 +151,8 @@ 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")) {
|
||||
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
|
||||
(seventh.startsWith("jsr c64flt.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
val nineth = pair[8].value.trimStart()
|
||||
val tenth = pair[9].value.trimStart()
|
||||
@ -164,7 +162,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val fourteenth = pair[13].value.trimStart()
|
||||
|
||||
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")) {
|
||||
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
|
||||
(fourteenth.startsWith("jsr c64flt.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
|
||||
|
||||
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
|
||||
// identical float init
|
||||
|
@ -1,729 +0,0 @@
|
||||
package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.toHex
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
internal fun translate(assign: Assignment) {
|
||||
if(assign.aug_op!=null)
|
||||
throw AssemblyError("aug-op assignments should have been transformed to normal ones")
|
||||
|
||||
when(assign.value) {
|
||||
is NumericLiteralValue -> {
|
||||
val numVal = assign.value as NumericLiteralValue
|
||||
when(numVal.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort())
|
||||
DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt())
|
||||
DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble())
|
||||
else -> throw AssemblyError("weird numval type")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
assignFromRegister(assign.target, (assign.value as RegisterExpr).register)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT)
|
||||
when(type) {
|
||||
DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, assign.value as IdentifierReference)
|
||||
DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, assign.value as IdentifierReference)
|
||||
DataType.FLOAT -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
|
||||
else -> throw AssemblyError("unsupported assignment target type $type")
|
||||
}
|
||||
}
|
||||
is AddressOf -> {
|
||||
val identifier = (assign.value as AddressOf).identifier
|
||||
assignFromAddressOf(assign.target, identifier)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
val read = (assign.value as DirectMemoryRead)
|
||||
when(read.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (read.addressExpression as NumericLiteralValue).number.toInt()
|
||||
assignFromMemoryByte(assign.target, address, null)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference)
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("missing asm gen for memread assignment into ${assign.target}")
|
||||
}
|
||||
}
|
||||
}
|
||||
is PrefixExpression -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateExpression(assign.value as PrefixExpression)
|
||||
assignFromEvalResult(assign.target)
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
// TODO optimize common cases
|
||||
asmgen.translateExpression(assign.value as BinaryExpression)
|
||||
assignFromEvalResult(assign.target)
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
// TODO optimize common cases
|
||||
val arrayExpr = assign.value as ArrayIndexedExpression
|
||||
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
|
||||
val index = arrayExpr.arrayspec.index
|
||||
if(index is NumericLiteralValue) {
|
||||
// constant array index value
|
||||
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
|
||||
val indexValue = index.number.toInt() * ArrayElementTypes.getValue(arrayDt).memorySize()
|
||||
when (arrayDt) {
|
||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B ->
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | dex")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W ->
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | lda $arrayVarName+$indexValue+1 | sta $ESTACK_HI_HEX,x | dex")
|
||||
DataType.ARRAY_F ->
|
||||
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
|
||||
else ->
|
||||
throw AssemblyError("weird array type")
|
||||
}
|
||||
} else {
|
||||
asmgen.translateArrayIndexIntoA(arrayExpr)
|
||||
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
|
||||
}
|
||||
assignFromEvalResult(assign.target)
|
||||
}
|
||||
is TypecastExpression -> {
|
||||
val cast = assign.value as TypecastExpression
|
||||
val sourceType = cast.expression.inferType(program)
|
||||
val targetType = assign.target.inferType(program, assign)
|
||||
if(sourceType.isKnown && targetType.isKnown &&
|
||||
(sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && targetType.typeOrElse(DataType.STRUCT) in ByteDatatypes) ||
|
||||
(sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && targetType.typeOrElse(DataType.STRUCT) in WordDatatypes)) {
|
||||
// no need for a type cast
|
||||
assign.value = cast.expression
|
||||
translate(assign)
|
||||
} else {
|
||||
asmgen.translateExpression(assign.value as TypecastExpression)
|
||||
assignFromEvalResult(assign.target)
|
||||
}
|
||||
}
|
||||
is FunctionCall -> {
|
||||
asmgen.translateExpression(assign.value as FunctionCall)
|
||||
assignFromEvalResult(assign.target)
|
||||
}
|
||||
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array assignment $assign")
|
||||
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened ${assign.value.position}")
|
||||
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values ${assign.value.position}")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromEvalResult(target: AssignTarget) {
|
||||
val targetIdent = target.identifier
|
||||
when {
|
||||
target.register!=null -> {
|
||||
if(target.register== Register.X)
|
||||
throw AssemblyError("can't pop into X register - use variable instead")
|
||||
asmgen.out(" inx | ld${target.register.name.toLowerCase()} $ESTACK_LO_HEX,x ")
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
val targetDt = targetIdent.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when(targetDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $targetName")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta $targetName
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta $targetName+1
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$targetName
|
||||
ldy #>$targetName
|
||||
jsr c64flt.pop_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type $targetDt")
|
||||
}
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
|
||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||
}
|
||||
target.arrayindexed!=null -> {
|
||||
val arrayDt = target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!.datatype
|
||||
val arrayVarName = asmgen.asmIdentifierName(target.arrayindexed!!.identifier)
|
||||
asmgen.translateExpression(target.arrayindexed!!.arrayspec.index)
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
popAndWriteArrayvalueWithIndexA(arrayDt, arrayVarName)
|
||||
}
|
||||
else -> throw AssemblyError("weird assignment target $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
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 ("."))
|
||||
}
|
||||
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
sta $targetName
|
||||
sty $targetName+1
|
||||
""")
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
throw AssemblyError("no asm gen for assign address $sourceName to memory word $target")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
throw AssemblyError("no asm gen for assign address $sourceName to array $targetName [ $index ]")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign address $sourceName to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmIdentifierName(variable)
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta $targetName
|
||||
sty $targetName+1
|
||||
""")
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
throw AssemblyError("no asm gen for assign wordvar $sourceName to memory ${target.memoryAddress}")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
asmgen.out(" lda $sourceName | sta $ESTACK_LO_HEX,x | lda $sourceName+1 | sta $ESTACK_HI_HEX,x | dex")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign wordvar to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmIdentifierName(variable)
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta $targetName
|
||||
lda $sourceName+1
|
||||
sta $targetName+1
|
||||
lda $sourceName+2
|
||||
sta $targetName+2
|
||||
lda $sourceName+3
|
||||
sta $targetName+3
|
||||
lda $sourceName+4
|
||||
sta $targetName+4
|
||||
""")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" lda #<$targetName | ldy #>$targetName | jsr c64flt.pop_float_to_indexed_var")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign floatvar to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmIdentifierName(variable)
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
target.register!=null -> {
|
||||
asmgen.out(" ld${target.register.name.toLowerCase()} $sourceName")
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta $targetName
|
||||
""")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
asmgen.out(" lda $sourceName | sta $ESTACK_LO_HEX,x | dex")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
|
||||
}
|
||||
target.memoryAddress != null -> {
|
||||
val addressExpr = target.memoryAddress.addressExpression
|
||||
val addressLv = addressExpr as? NumericLiteralValue
|
||||
when {
|
||||
addressLv != null -> asmgen.out(" lda $sourceName | sta ${addressLv.number.toHex()}")
|
||||
addressExpr is IdentifierReference -> {
|
||||
val targetName = asmgen.asmIdentifierName(addressExpr)
|
||||
asmgen.out(" lda $sourceName | sta $targetName")
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(addressExpr)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
ldy $ESTACK_HI_HEX,x
|
||||
sta (+) +1
|
||||
sty (+) +2
|
||||
lda $sourceName
|
||||
+ sta ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign bytevar to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromRegister(target: AssignTarget, register: Register) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out(" st${register.name.toLowerCase()} $targetName")
|
||||
}
|
||||
target.register!=null -> {
|
||||
when(register) {
|
||||
Register.A -> when(target.register) {
|
||||
Register.A -> {}
|
||||
Register.X -> asmgen.out(" tax")
|
||||
Register.Y -> asmgen.out(" tay")
|
||||
}
|
||||
Register.X -> when(target.register) {
|
||||
Register.A -> asmgen.out(" txa")
|
||||
Register.X -> {}
|
||||
Register.Y -> asmgen.out(" txy")
|
||||
}
|
||||
Register.Y -> when(target.register) {
|
||||
Register.A -> asmgen.out(" tya")
|
||||
Register.X -> asmgen.out(" tyx")
|
||||
Register.Y -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
storeRegisterInMemoryAddress(register, target.memoryAddress)
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
when (index) {
|
||||
is NumericLiteralValue -> {
|
||||
val memindex = index.number.toInt()
|
||||
when(register) {
|
||||
Register.A -> asmgen.out(" sta $targetName+$memindex")
|
||||
Register.X -> asmgen.out(" stx $targetName+$memindex")
|
||||
Register.Y -> asmgen.out(" sty $targetName+$memindex")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when(register) {
|
||||
Register.A -> asmgen.out(" sta ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.X -> asmgen.out(" stx ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.Y -> asmgen.out(" sty ${C64Zeropage.SCRATCH_B1}")
|
||||
}
|
||||
when(index.register) {
|
||||
Register.A -> {}
|
||||
Register.X -> asmgen.out(" txa")
|
||||
Register.Y -> asmgen.out(" tya")
|
||||
}
|
||||
asmgen.out("""
|
||||
tay
|
||||
lda ${C64Zeropage.SCRATCH_B1}
|
||||
sta $targetName,y
|
||||
""")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
when(register) {
|
||||
Register.A -> asmgen.out(" sta ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.X -> asmgen.out(" stx ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.Y -> asmgen.out(" sty ${C64Zeropage.SCRATCH_B1}")
|
||||
}
|
||||
asmgen.out("""
|
||||
lda ${asmgen.asmIdentifierName(index)}
|
||||
tay
|
||||
lda ${C64Zeropage.SCRATCH_B1}
|
||||
sta $targetName,y
|
||||
""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.saveRegister(register)
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.restoreRegister(register)
|
||||
when(register) {
|
||||
Register.A -> asmgen.out(" sta ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.X -> asmgen.out(" stx ${C64Zeropage.SCRATCH_B1}")
|
||||
Register.Y -> asmgen.out(" sty ${C64Zeropage.SCRATCH_B1}")
|
||||
}
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
tay
|
||||
lda ${C64Zeropage.SCRATCH_B1}
|
||||
sta $targetName,y
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign register $register to $target")
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeRegisterInMemoryAddress(register: Register, memoryAddress: DirectMemoryWrite) {
|
||||
val addressExpr = memoryAddress.addressExpression
|
||||
val addressLv = addressExpr as? NumericLiteralValue
|
||||
val registerName = register.name.toLowerCase()
|
||||
when {
|
||||
addressLv != null -> asmgen.out(" st$registerName ${addressLv.number.toHex()}")
|
||||
addressExpr is IdentifierReference -> {
|
||||
val targetName = asmgen.asmIdentifierName(addressExpr)
|
||||
when(register) {
|
||||
Register.A -> asmgen.out("""
|
||||
ldy $targetName
|
||||
sty (+) +1
|
||||
ldy $targetName+1
|
||||
sty (+) +2
|
||||
+ sta ${'$'}ffff ; modified""")
|
||||
Register.X -> asmgen.out("""
|
||||
ldy $targetName
|
||||
sty (+) +1
|
||||
ldy $targetName+1
|
||||
sty (+) +2
|
||||
+ stx ${'$'}ffff ; modified""")
|
||||
Register.Y -> asmgen.out("""
|
||||
lda $targetName
|
||||
sta (+) +1
|
||||
lda $targetName+1
|
||||
sta (+) +2
|
||||
+ sty ${'$'}ffff ; modified""")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.saveRegister(register)
|
||||
asmgen.translateExpression(addressExpr)
|
||||
asmgen.restoreRegister(register)
|
||||
when (register) {
|
||||
Register.A -> asmgen.out(" tay")
|
||||
Register.X -> throw AssemblyError("can't use X register here")
|
||||
Register.Y -> {}
|
||||
}
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta (+) +1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) +2
|
||||
+ sty ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromWordConstant(target: AssignTarget, word: Int) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
if(word ushr 8 == word and 255) {
|
||||
// lsb=msb
|
||||
asmgen.out("""
|
||||
lda #${(word and 255).toHex()}
|
||||
sta $targetName
|
||||
sta $targetName+1
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda #<${word.toHex()}
|
||||
ldy #>${word.toHex()}
|
||||
sta $targetName
|
||||
sty $targetName+1
|
||||
""")
|
||||
}
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
throw AssemblyError("no asm gen for assign word $word to memory ${target.memoryAddress}")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
// TODO optimize common cases
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
asl a
|
||||
tay
|
||||
lda #<${word.toHex()}
|
||||
sta $targetName,y
|
||||
lda #>${word.toHex()}
|
||||
sta $targetName+1,y
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign word $word to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromByteConstant(target: AssignTarget, byte: Short) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
when {
|
||||
target.register!=null -> {
|
||||
asmgen.out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}")
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out(" lda #${byte.toHex()} | sta $targetName ")
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
asmgen.out(" ldy #${byte.toHex()}")
|
||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
// TODO optimize common cases
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
inx
|
||||
ldy $ESTACK_LO_HEX,x
|
||||
lda #${byte.toHex()}
|
||||
sta $targetName,y
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign byte $byte to $target")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromFloatConstant(target: AssignTarget, float: Double) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
if(float==0.0) {
|
||||
// optimized case for float zero
|
||||
when {
|
||||
targetIdent != null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta $targetName
|
||||
sta $targetName+1
|
||||
sta $targetName+2
|
||||
sta $targetName+3
|
||||
sta $targetName+4
|
||||
""")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
if(index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * C64MachineDefinition.FLOAT_MEM_SIZE
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta $targetName+$indexValue
|
||||
sta $targetName+$indexValue+1
|
||||
sta $targetName+$indexValue+2
|
||||
sta $targetName+$indexValue+3
|
||||
sta $targetName+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
lda #<${targetName}
|
||||
sta ${C64Zeropage.SCRATCH_W1}
|
||||
lda #>${targetName}
|
||||
sta ${C64Zeropage.SCRATCH_W1 + 1}
|
||||
jsr c64flt.set_0_array_float
|
||||
""")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign float 0.0 to $target")
|
||||
}
|
||||
} else {
|
||||
// non-zero value
|
||||
val constFloat = asmgen.getFloatConst(float)
|
||||
when {
|
||||
targetIdent != null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta $targetName
|
||||
lda $constFloat+1
|
||||
sta $targetName+1
|
||||
lda $constFloat+2
|
||||
sta $targetName+2
|
||||
lda $constFloat+3
|
||||
sta $targetName+3
|
||||
lda $constFloat+4
|
||||
sta $targetName+4
|
||||
""")
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val arrayVarName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
if(index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * C64MachineDefinition.FLOAT_MEM_SIZE
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta $arrayVarName+$indexValue
|
||||
lda $constFloat+1
|
||||
sta $arrayVarName+$indexValue+1
|
||||
lda $constFloat+2
|
||||
sta $arrayVarName+$indexValue+2
|
||||
lda $constFloat+3
|
||||
sta $arrayVarName+$indexValue+3
|
||||
lda $constFloat+4
|
||||
sta $arrayVarName+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
asmgen.out("""
|
||||
lda #<${constFloat}
|
||||
sta ${C64Zeropage.SCRATCH_W1}
|
||||
lda #>${constFloat}
|
||||
sta ${C64Zeropage.SCRATCH_W1 + 1}
|
||||
lda #<${arrayVarName}
|
||||
sta ${C64Zeropage.SCRATCH_W2}
|
||||
lda #>${arrayVarName}
|
||||
sta ${C64Zeropage.SCRATCH_W2 + 1}
|
||||
jsr c64flt.set_array_float
|
||||
""")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign float $float to $target")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) {
|
||||
val targetIdent = target.identifier
|
||||
val targetArrayIdx = target.arrayindexed
|
||||
if(address!=null) {
|
||||
when {
|
||||
target.register!=null -> {
|
||||
asmgen.out(" ld${target.register.name.toLowerCase()} ${address.toHex()}")
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda ${address.toHex()}
|
||||
sta $targetName
|
||||
""")
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
asmgen.out(" ldy ${address.toHex()}")
|
||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
throw AssemblyError("no asm gen for assign memory byte at $address to array $targetName [ $index ]")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign memory byte $target")
|
||||
}
|
||||
}
|
||||
else if(identifier!=null) {
|
||||
val sourceName = asmgen.asmIdentifierName(identifier)
|
||||
when {
|
||||
target.register!=null -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta (+) + 1
|
||||
lda $sourceName+1
|
||||
sta (+) + 2""")
|
||||
when(target.register){
|
||||
Register.A -> asmgen.out("+ lda ${'$'}ffff\t; modified")
|
||||
Register.X -> asmgen.out("+ ldx ${'$'}ffff\t; modified")
|
||||
Register.Y -> asmgen.out("+ ldy ${'$'}ffff\t; modified")
|
||||
}
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val targetName = asmgen.asmIdentifierName(targetIdent)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta (+) + 1
|
||||
lda $sourceName+1
|
||||
sta (+) + 2
|
||||
+ lda ${'$'}ffff\t; modified
|
||||
sta $targetName""")
|
||||
}
|
||||
target.memoryAddress!=null -> {
|
||||
asmgen.out(" ldy $sourceName")
|
||||
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
throw AssemblyError("no asm gen for assign memory byte $sourceName to array $targetName [ $index ]")
|
||||
}
|
||||
else -> throw AssemblyError("no asm gen for assign memory byte $target")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun popAndWriteArrayvalueWithIndexA(arrayDt: DataType, variablename: String) {
|
||||
when (arrayDt) {
|
||||
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B ->
|
||||
asmgen.out(" tay | inx | lda $ESTACK_LO_HEX,x | sta $variablename,y")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W ->
|
||||
asmgen.out(" asl a | tay | inx | lda $ESTACK_LO_HEX,x | sta $variablename,y | lda $ESTACK_HI_HEX,x | sta $variablename+1,y")
|
||||
DataType.ARRAY_F ->
|
||||
// index * 5 is done in the subroutine that's called
|
||||
asmgen.out("""
|
||||
sta $ESTACK_LO_HEX,x
|
||||
dex
|
||||
lda #<$variablename
|
||||
ldy #>$variablename
|
||||
jsr c64flt.pop_float_to_indexed_var
|
||||
""")
|
||||
else ->
|
||||
throw AssemblyError("weird array type")
|
||||
}
|
||||
}
|
||||
}
|
@ -2,19 +2,10 @@ package prog8.compiler.target.c64.codegen
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ByteDatatypes
|
||||
import prog8.ast.base.DataType
|
||||
import prog8.ast.base.Register
|
||||
import prog8.ast.base.WordDatatypes
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||
import prog8.compiler.toHex
|
||||
import prog8.functions.FSignature
|
||||
|
||||
@ -39,6 +30,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
|
||||
when (functionName) {
|
||||
"msb" -> funcMsb(fcall)
|
||||
"lsb" -> funcLsb(fcall)
|
||||
"mkword" -> funcMkword(fcall, func)
|
||||
"abs" -> funcAbs(fcall, func)
|
||||
"swap" -> funcSwap(fcall)
|
||||
@ -50,8 +42,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
"ln", "log2", "sqrt", "rad",
|
||||
"deg", "round", "floor", "ceil",
|
||||
"rdnf" -> funcVariousFloatFuncs(fcall, func, functionName)
|
||||
"lsl" -> funcLsl(fcall)
|
||||
"lsr" -> funcLsr(fcall)
|
||||
"rol" -> funcRol(fcall)
|
||||
"rol2" -> funcRol2(fcall)
|
||||
"ror" -> funcRor(fcall)
|
||||
@ -61,7 +51,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
"rsave" -> {
|
||||
// save cpu status flag and all registers A, X, Y.
|
||||
// see http://6502.org/tutorials/register_preservation.html
|
||||
asmgen.out(" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}")
|
||||
asmgen.out(" php | sta P8ZP_SCRATCH_REG | pha | txa | pha | tya | pha | lda P8ZP_SCRATCH_REG")
|
||||
}
|
||||
"rrestore" -> {
|
||||
// restore all registers and cpu status flag
|
||||
@ -82,15 +72,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val variable = fcall.args.single()
|
||||
if (variable is IdentifierReference) {
|
||||
val decl = variable.targetVarDecl(program.namespace)!!
|
||||
val varName = asmgen.asmIdentifierName(variable)
|
||||
val numElements = decl.arraysize!!.size()
|
||||
val varName = asmgen.asmVariableName(variable)
|
||||
val numElements = decl.arraysize!!.constIndex()
|
||||
when (decl.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta ${C64Zeropage.SCRATCH_W1}
|
||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.reverse_b
|
||||
""")
|
||||
@ -99,8 +89,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta ${C64Zeropage.SCRATCH_W1}
|
||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.reverse_w
|
||||
""")
|
||||
@ -109,8 +99,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta ${C64Zeropage.SCRATCH_W1}
|
||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
jsr prog8_lib.reverse_f
|
||||
""")
|
||||
@ -124,17 +114,17 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
val variable = fcall.args.single()
|
||||
if (variable is IdentifierReference) {
|
||||
val decl = variable.targetVarDecl(program.namespace)!!
|
||||
val varName = asmgen.asmIdentifierName(variable)
|
||||
val numElements = decl.arraysize!!.size()
|
||||
val varName = asmgen.asmVariableName(variable)
|
||||
val numElements = decl.arraysize!!.constIndex()
|
||||
when (decl.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta ${C64Zeropage.SCRATCH_W1}
|
||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
sta ${C64Zeropage.SCRATCH_B1}
|
||||
sta P8ZP_SCRATCH_B1
|
||||
""")
|
||||
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
|
||||
}
|
||||
@ -142,10 +132,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
ldy #>$varName
|
||||
sta ${C64Zeropage.SCRATCH_W1}
|
||||
sty ${C64Zeropage.SCRATCH_W1 + 1}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements
|
||||
sta ${C64Zeropage.SCRATCH_B1}
|
||||
sta P8ZP_SCRATCH_B1
|
||||
""")
|
||||
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
|
||||
}
|
||||
@ -176,15 +166,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" lsr a | bcc + | ora #\$80 |+ ")
|
||||
Register.X -> asmgen.out(" txa | lsr a | bcc + | ora #\$80 |+ tax ")
|
||||
Register.Y -> asmgen.out(" tya | lsr a | bcc + | ora #\$80 |+ tay ")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -198,7 +181,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.ror2_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -227,23 +210,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.translateExpression(what.addressExpression)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
lda P8ESTACK_LO,x
|
||||
sta (+) + 1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
lda P8ESTACK_HI,x
|
||||
sta (+) + 2
|
||||
+ ror ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" ror a")
|
||||
Register.X -> asmgen.out(" txa | ror a | tax")
|
||||
Register.Y -> asmgen.out(" tya | ror a | tay")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" ror $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -257,7 +233,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.ror_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" ror $variable+1 | ror $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -287,15 +263,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" cmp #\$80 | rol a ")
|
||||
Register.X -> asmgen.out(" txa | cmp #\$80 | rol a | tax")
|
||||
Register.Y -> asmgen.out(" tya | cmp #\$80 | rol a | tay")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -309,7 +278,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.rol2_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -338,23 +307,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.translateExpression(what.addressExpression)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
lda P8ESTACK_LO,x
|
||||
sta (+) + 1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
lda P8ESTACK_HI,x
|
||||
sta (+) + 2
|
||||
+ rol ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" rol a")
|
||||
Register.X -> asmgen.out(" txa | rol a | tax")
|
||||
Register.Y -> asmgen.out(" tya | rol a | tay")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" rol $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -368,7 +330,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
asmgen.out(" jsr prog8_lib.rol_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" rol $variable | rol $variable+1")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -378,146 +340,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcLsr(fcall: IFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
val dt = what.inferType(program)
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" lsr a")
|
||||
Register.X -> asmgen.out(" txa | lsr a | tax")
|
||||
Register.Y -> asmgen.out(" tya | lsr a | tay")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteralValue) {
|
||||
val number = (what.addressExpression as NumericLiteralValue).number
|
||||
asmgen.out(" lsr ${number.toHex()}")
|
||||
} else {
|
||||
asmgen.translateExpression(what.addressExpression)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta (+) + 1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) + 2
|
||||
+ lsr ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.out(" jsr prog8_lib.lsr_array_ub")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.out(" jsr prog8_lib.lsr_array_b")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
asmgen.out(" lda $variable | asl a | ror $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.out(" jsr prog8_lib.lsr_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
asmgen.out(" lsr $variable+1 | ror $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.out(" jsr prog8_lib.lsr_array_w")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
asmgen.out(" lda $variable+1 | asl a | ror $variable+1 | ror $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcLsl(fcall: IFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
val dt = what.inferType(program)
|
||||
when (dt.typeOrElse(DataType.STRUCT)) {
|
||||
in ByteDatatypes -> {
|
||||
when (what) {
|
||||
is RegisterExpr -> {
|
||||
when (what.register) {
|
||||
Register.A -> asmgen.out(" asl a")
|
||||
Register.X -> asmgen.out(" txa | asl a | tax")
|
||||
Register.Y -> asmgen.out(" tya | asl a | tay")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteralValue) {
|
||||
val number = (what.addressExpression as NumericLiteralValue).number
|
||||
asmgen.out(" asl ${number.toHex()}")
|
||||
} else {
|
||||
asmgen.translateExpression(what.addressExpression)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
sta (+) + 1
|
||||
lda $ESTACK_HI_HEX,x
|
||||
sta (+) + 2
|
||||
+ asl ${'$'}ffff ; modified
|
||||
""")
|
||||
}
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.out(" jsr prog8_lib.lsl_array_b")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
asmgen.translateExpression(what.identifier)
|
||||
asmgen.translateExpression(what.arrayspec.index)
|
||||
asmgen.out(" jsr prog8_lib.lsl_array_w")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val variable = asmgen.asmIdentifierName(what)
|
||||
asmgen.out(" asl $variable | rol $variable+1")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) {
|
||||
translateFunctionArguments(fcall.args, func)
|
||||
asmgen.out(" jsr c64flt.func_$functionName")
|
||||
@ -561,20 +383,57 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun funcStrlen(fcall: IFunctionCall) {
|
||||
outputPushAddressOfIdentifier(fcall.args[0])
|
||||
asmgen.out(" jsr prog8_lib.func_strlen")
|
||||
val name = asmgen.asmVariableName(fcall.args[0] as IdentifierReference)
|
||||
asmgen.out("""
|
||||
lda #<$name
|
||||
ldy #>$name
|
||||
jsr prog8_lib.strlen
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
|
||||
private fun funcSwap(fcall: IFunctionCall) {
|
||||
val first = fcall.args[0]
|
||||
val second = fcall.args[1]
|
||||
asmgen.translateExpression(first)
|
||||
asmgen.translateExpression(second)
|
||||
// pop in reverse order
|
||||
val firstTarget = AssignTarget.fromExpr(first)
|
||||
val secondTarget = AssignTarget.fromExpr(second)
|
||||
asmgen.assignFromEvalResult(firstTarget)
|
||||
asmgen.assignFromEvalResult(secondTarget)
|
||||
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")
|
||||
return
|
||||
}
|
||||
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
|
||||
asmgen.out("""
|
||||
ldy $firstName
|
||||
lda $secondName
|
||||
sta $firstName
|
||||
sty $secondName
|
||||
ldy $firstName+1
|
||||
lda $secondName+1
|
||||
sta $firstName+1
|
||||
sty $secondName+1
|
||||
""")
|
||||
return
|
||||
}
|
||||
if(dt.istype(DataType.FLOAT)) {
|
||||
asmgen.out("""
|
||||
lda #<$firstName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>$firstName
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<$secondName
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>$secondName
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr c64flt.swap_floats
|
||||
""")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// other types of swap() calls should have been replaced by a different statement sequence involving a temp variable
|
||||
throw AssemblyError("no asm generation for swap funccall $fcall")
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
|
||||
@ -589,8 +448,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
}
|
||||
|
||||
private fun funcMkword(fcall: IFunctionCall, func: FSignature) {
|
||||
translateFunctionArguments(fcall.args, func)
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x")
|
||||
// trick: push the args in reverse order (msb first, lsb second) this saves some instructions
|
||||
asmgen.translateExpression(fcall.args[1])
|
||||
asmgen.translateExpression(fcall.args[0])
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta P8ESTACK_HI+1,x")
|
||||
}
|
||||
|
||||
private fun funcMsb(fcall: IFunctionCall) {
|
||||
@ -598,39 +459,43 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
|
||||
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
||||
throw AssemblyError("msb required word argument")
|
||||
if (arg is NumericLiteralValue)
|
||||
throw AssemblyError("should have been const-folded")
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
if (arg is IdentifierReference) {
|
||||
val sourceName = asmgen.asmIdentifierName(arg)
|
||||
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
|
||||
} else {
|
||||
asmgen.translateExpression(arg)
|
||||
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x")
|
||||
asmgen.out(" lda P8ESTACK_HI+1,x | sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcLsb(fcall: IFunctionCall) {
|
||||
val arg = fcall.args.single()
|
||||
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
|
||||
throw AssemblyError("lsb required word argument")
|
||||
if (arg is NumericLiteralValue)
|
||||
throw AssemblyError("lsb(const) should have been const-folded away")
|
||||
if (arg is IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
|
||||
} else {
|
||||
asmgen.translateExpression(arg)
|
||||
// just ignore any high-byte
|
||||
}
|
||||
}
|
||||
|
||||
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
|
||||
arg as IdentifierReference
|
||||
val identifierName = asmgen.asmIdentifierName(arg)
|
||||
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.size()!!
|
||||
val identifierName = asmgen.asmVariableName(arg)
|
||||
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.constIndex()!!
|
||||
asmgen.out("""
|
||||
lda #<$identifierName
|
||||
sta $ESTACK_LO_HEX,x
|
||||
sta P8ESTACK_LO,x
|
||||
lda #>$identifierName
|
||||
sta $ESTACK_HI_HEX,x
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
lda #$size
|
||||
sta $ESTACK_LO_HEX,x
|
||||
dex
|
||||
""")
|
||||
}
|
||||
|
||||
private fun outputPushAddressOfIdentifier(arg: Expression) {
|
||||
val identifierName = asmgen.asmIdentifierName(arg as IdentifierReference)
|
||||
asmgen.out("""
|
||||
lda #<$identifierName
|
||||
sta $ESTACK_LO_HEX,x
|
||||
lda #>$identifierName
|
||||
sta $ESTACK_HI_HEX,x
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
""")
|
||||
}
|
||||
|
@ -4,12 +4,8 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS2_HEX
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.toHex
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import kotlin.math.absoluteValue
|
||||
@ -20,16 +16,14 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
when(expression) {
|
||||
is PrefixExpression -> translateExpression(expression)
|
||||
is BinaryExpression -> translateExpression(expression)
|
||||
is ArrayIndexedExpression -> translatePushFromArray(expression)
|
||||
is ArrayIndexedExpression -> translateExpression(expression)
|
||||
is TypecastExpression -> translateExpression(expression)
|
||||
is AddressOf -> translateExpression(expression)
|
||||
is DirectMemoryRead -> translateExpression(expression)
|
||||
is NumericLiteralValue -> translateExpression(expression)
|
||||
is RegisterExpr -> translateExpression(expression)
|
||||
is IdentifierReference -> translateExpression(expression)
|
||||
is FunctionCall -> translateExpression(expression)
|
||||
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array assignment")
|
||||
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
|
||||
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
|
||||
}
|
||||
}
|
||||
@ -40,18 +34,42 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
if (builtinFunc != null) {
|
||||
asmgen.translateFunctioncallExpression(expression, builtinFunc)
|
||||
} else {
|
||||
asmgen.translateFunctionCall(expression)
|
||||
val sub = expression.target.targetSubroutine(program.namespace)!!
|
||||
asmgen.translateFunctionCall(expression)
|
||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||
for ((_, reg) in returns) {
|
||||
if (!reg.stack) {
|
||||
// result value in cpu or status registers, put it on the stack
|
||||
if (reg.registerOrPair != null) {
|
||||
when (reg.registerOrPair) {
|
||||
RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
|
||||
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable")
|
||||
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
|
||||
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||
RegisterOrPair.X -> {
|
||||
// return value in X register has been discarded, just push a zero
|
||||
if(CompilationTarget.machine.cpu==CpuType.CPU65c02)
|
||||
asmgen.out(" stz P8ESTACK_LO,x")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_LO,x")
|
||||
asmgen.out(" dex")
|
||||
}
|
||||
RegisterOrPair.AX -> {
|
||||
// return value in X register has been discarded, just push a zero in this place
|
||||
asmgen.out(" sta P8ESTACK_LO,x")
|
||||
if(CompilationTarget.machine.cpu==CpuType.CPU65c02)
|
||||
asmgen.out(" stz P8ESTACK_HI,x")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_HI,x")
|
||||
asmgen.out(" dex")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
// return value in X register has been discarded, just push a zero in this place
|
||||
if(CompilationTarget.machine.cpu==CpuType.CPU65c02)
|
||||
asmgen.out(" stz P8ESTACK_LO,x")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_LO,x")
|
||||
asmgen.out(" tya | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
}
|
||||
}
|
||||
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
|
||||
@ -66,7 +84,12 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
DataType.UBYTE -> {
|
||||
when(expr.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out(" lda #0 | sta $ESTACK_HI_PLUS1_HEX,x")
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
if(CompilationTarget.machine.cpu==CpuType.CPU65c02)
|
||||
asmgen.out(" stz P8ESTACK_HI+1,x")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||
}
|
||||
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_ub2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -75,7 +98,20 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
DataType.BYTE -> {
|
||||
when(expr.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | ${asmgen.signExtendAtoMsb("$ESTACK_HI_PLUS1_HEX,x")}")
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
// sign extend
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
ora #$7f
|
||||
bmi +""")
|
||||
if(CompilationTarget.machine.cpu==CpuType.CPU65c02)
|
||||
asmgen.out("""
|
||||
+ stz P8ESTACK_HI+1,x""")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
+ sta P8ESTACK_HI+1,x""")
|
||||
}
|
||||
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_b2float")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -110,82 +146,67 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else")
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: AddressOf) {
|
||||
val name = asmgen.asmIdentifierName(expr.identifier)
|
||||
asmgen.out(" lda #<$name | sta $ESTACK_LO_HEX,x | lda #>$name | sta $ESTACK_HI_HEX,x | dex")
|
||||
val name = asmgen.asmVariableName(expr.identifier)
|
||||
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: DirectMemoryRead) {
|
||||
when(expr.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (expr.addressExpression as NumericLiteralValue).number.toInt()
|
||||
asmgen.out(" lda ${address.toHex()} | sta $ESTACK_LO_HEX,x | dex")
|
||||
asmgen.out(" lda ${address.toHex()} | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
// the identifier is a pointer variable, so read the value from the address in it
|
||||
val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta (+) +1
|
||||
lda $sourceName+1
|
||||
sta (+) +2
|
||||
+ lda ${'$'}ffff ; modified
|
||||
sta $ESTACK_LO_HEX,x
|
||||
dex""")
|
||||
asmgen.loadByteFromPointerIntoA(expr.addressExpression as IdentifierReference)
|
||||
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
else -> {
|
||||
translateExpression(expr.addressExpression)
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address")
|
||||
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack")
|
||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: NumericLiteralValue) {
|
||||
when(expr.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta $ESTACK_LO_HEX,x | dex")
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
||||
lda #<${expr.number.toHex()}
|
||||
sta $ESTACK_LO_HEX,x
|
||||
sta P8ESTACK_LO,x
|
||||
lda #>${expr.number.toHex()}
|
||||
sta $ESTACK_HI_HEX,x
|
||||
sta P8ESTACK_HI,x
|
||||
dex
|
||||
""")
|
||||
DataType.FLOAT -> {
|
||||
val floatConst = asmgen.getFloatConst(expr.number.toDouble())
|
||||
val floatConst = asmgen.getFloatAsmConst(expr.number.toDouble())
|
||||
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: RegisterExpr) {
|
||||
when(expr.register) {
|
||||
Register.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
|
||||
Register.X -> asmgen.out(" txa | sta $ESTACK_LO_HEX,x | dex")
|
||||
Register.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: IdentifierReference) {
|
||||
val varname = asmgen.asmIdentifierName(expr)
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out(" lda $varname | sta $ESTACK_LO_HEX,x | dex")
|
||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out(" lda $varname | sta $ESTACK_LO_HEX,x | lda $varname+1 | sta $ESTACK_HI_HEX,x | dex")
|
||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | lda $varname+1 | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$varname | ldy #>$varname| jsr c64flt.push_float")
|
||||
}
|
||||
in IterableDatatypes -> {
|
||||
asmgen.out(" lda #<$varname | sta $ESTACK_LO_HEX,x | lda #>$varname | sta $ESTACK_HI_HEX,x | dex")
|
||||
asmgen.out(" lda #<$varname | sta P8ESTACK_LO,x | lda #>$varname | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
else -> throw AssemblyError("stack push weird variable type $expr")
|
||||
}
|
||||
@ -193,7 +214,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
|
||||
private val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40)
|
||||
private val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40)
|
||||
private val powersOfTwo = setOf(0,1,2,4,8,16,32,64,128,256)
|
||||
|
||||
private fun translateExpression(expr: BinaryExpression) {
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
@ -206,65 +226,80 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
// see if we can apply some optimized routines
|
||||
when(expr.operator) {
|
||||
">>" -> {
|
||||
// bit-shifts are always by a constant number (for now)
|
||||
translateExpression(expr.left)
|
||||
val amount = expr.right.constValue(program)!!.number.toInt()
|
||||
when (leftDt) {
|
||||
DataType.UBYTE -> {
|
||||
if(amount<=2)
|
||||
repeat(amount) { asmgen.out(" lsr $ESTACK_LO_PLUS1_HEX,x") }
|
||||
else {
|
||||
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x")
|
||||
repeat(amount) { asmgen.out(" lsr a") }
|
||||
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
|
||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||
if(amount!=null) {
|
||||
when (leftDt) {
|
||||
DataType.UBYTE -> {
|
||||
if (amount <= 2)
|
||||
repeat(amount) { asmgen.out(" lsr P8ESTACK_LO+1,x") }
|
||||
else {
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x")
|
||||
repeat(amount) { asmgen.out(" lsr a") }
|
||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(amount<=2)
|
||||
repeat(amount) { asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||
else {
|
||||
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | sta ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}")
|
||||
repeat(amount) { asmgen.out(" asl a | ror ${C64MachineDefinition.C64Zeropage.SCRATCH_B1} | lda ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}") }
|
||||
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
|
||||
DataType.BYTE -> {
|
||||
if (amount <= 2)
|
||||
repeat(amount) { asmgen.out(" lda P8ESTACK_LO+1,x | asl a | ror P8ESTACK_LO+1,x") }
|
||||
else {
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x | sta P8ZP_SCRATCH_B1")
|
||||
repeat(amount) { asmgen.out(" asl a | ror P8ZP_SCRATCH_B1 | lda P8ZP_SCRATCH_B1") }
|
||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
var left = amount
|
||||
while (left >= 7) {
|
||||
asmgen.out(" jsr math.shift_right_uw_7")
|
||||
left -= 7
|
||||
}
|
||||
if (left in 0..2)
|
||||
repeat(left) { asmgen.out(" lsr P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_uw_$left")
|
||||
}
|
||||
DataType.WORD -> {
|
||||
var left = amount
|
||||
while (left >= 7) {
|
||||
asmgen.out(" jsr math.shift_right_w_7")
|
||||
left -= 7
|
||||
}
|
||||
if (left in 0..2)
|
||||
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_w_$left")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(amount<=2)
|
||||
repeat(amount) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_uw_$amount") // 3-7 (8+ is done via other optimizations)
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(amount<=2)
|
||||
repeat(amount) { asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_right_w_$amount") // 3-7 (8+ is done via other optimizations)
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
"<<" -> {
|
||||
// bit-shifts are always by a constant number (for now)
|
||||
translateExpression(expr.left)
|
||||
val amount = expr.right.constValue(program)!!.number.toInt()
|
||||
if (leftDt in ByteDatatypes) {
|
||||
if(amount<=2)
|
||||
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x") }
|
||||
else {
|
||||
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x")
|
||||
repeat(amount) { asmgen.out(" asl a") }
|
||||
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(amount<=2) {
|
||||
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
|
||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||
if(amount!=null) {
|
||||
if (leftDt in ByteDatatypes) {
|
||||
if (amount <= 2)
|
||||
repeat(amount) { asmgen.out(" asl P8ESTACK_LO+1,x") }
|
||||
else {
|
||||
asmgen.out(" lda P8ESTACK_LO+1,x")
|
||||
repeat(amount) { asmgen.out(" asl a") }
|
||||
asmgen.out(" sta P8ESTACK_LO+1,x")
|
||||
}
|
||||
} else {
|
||||
asmgen.out(" jsr math.shift_left_w_$amount") // 3-7 (8+ is done via other optimizations)
|
||||
var left = amount
|
||||
while (left >= 7) {
|
||||
asmgen.out(" jsr math.shift_left_w_7")
|
||||
left -= 7
|
||||
}
|
||||
if (left in 0..2)
|
||||
repeat(left) { asmgen.out(" asl P8ESTACK_LO+1,x | rol P8ESTACK_HI+1,x") }
|
||||
else
|
||||
asmgen.out(" jsr math.shift_left_w_$left")
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
"*" -> {
|
||||
val value = expr.right.constValue(program)
|
||||
@ -320,8 +355,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
// the general, non-optimized cases
|
||||
translateExpression(expr.left)
|
||||
translateExpression(expr.right)
|
||||
if(leftDt!=rightDt)
|
||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always?
|
||||
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|
||||
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
|
||||
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
|
||||
|
||||
when (leftDt) {
|
||||
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
|
||||
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
|
||||
@ -347,9 +384,9 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
when(type) {
|
||||
in ByteDatatypes ->
|
||||
asmgen.out("""
|
||||
lda $ESTACK_LO_PLUS1_HEX,x
|
||||
lda P8ESTACK_LO+1,x
|
||||
eor #255
|
||||
sta $ESTACK_LO_PLUS1_HEX,x
|
||||
sta P8ESTACK_LO+1,x
|
||||
""")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -366,29 +403,47 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
}
|
||||
}
|
||||
|
||||
private fun translatePushFromArray(arrayExpr: ArrayIndexedExpression) {
|
||||
// assume *reading* from an array
|
||||
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
|
||||
val index = arrayExpr.arrayspec.index
|
||||
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
|
||||
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
|
||||
val elementDt = arrayExpr.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val arrayVarName = asmgen.asmVariableName(arrayExpr.identifier)
|
||||
if(index is NumericLiteralValue) {
|
||||
val elementDt = ArrayElementTypes.getValue(arrayDt)
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | dex")
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | lda $arrayVarName+$indexValue+1 | sta $ESTACK_HI_HEX,x | dex")
|
||||
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")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
else -> throw AssemblyError("weird element type")
|
||||
}
|
||||
} else {
|
||||
asmgen.translateArrayIndexIntoA(arrayExpr)
|
||||
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | lda $arrayVarName+1,y | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>$arrayVarName
|
||||
clc
|
||||
adc #<$arrayVarName
|
||||
bcc +
|
||||
iny
|
||||
+ jsr c64flt.push_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,20 +458,21 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
|
||||
asmgen.out(" jsr prog8_lib.remainder_ub")
|
||||
}
|
||||
"+" -> asmgen.out("""
|
||||
lda $ESTACK_LO_PLUS2_HEX,x
|
||||
lda P8ESTACK_LO+2,x
|
||||
clc
|
||||
adc $ESTACK_LO_PLUS1_HEX,x
|
||||
adc P8ESTACK_LO+1,x
|
||||
inx
|
||||
sta $ESTACK_LO_PLUS1_HEX,x
|
||||
sta P8ESTACK_LO+1,x
|
||||
""")
|
||||
"-" -> asmgen.out("""
|
||||
lda $ESTACK_LO_PLUS2_HEX,x
|
||||
lda P8ESTACK_LO+2,x
|
||||
sec
|
||||
sbc $ESTACK_LO_PLUS1_HEX,x
|
||||
sbc P8ESTACK_LO+1,x
|
||||
inx
|
||||
sta $ESTACK_LO_PLUS1_HEX,x
|
||||
sta P8ESTACK_LO+1,x
|
||||
""")
|
||||
"<<", ">>" -> throw AssemblyError("bit-shifts not via stack")
|
||||
"<<" -> asmgen.out(" jsr prog8_lib.shiftleft_b")
|
||||
">>" -> asmgen.out(" jsr prog8_lib.shiftright_b")
|
||||
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
|
||||
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
|
||||
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,13 +4,10 @@ import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
|
||||
import prog8.compiler.toHex
|
||||
import prog8.compiler.target.c64.codegen.assignment.*
|
||||
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
@ -19,212 +16,179 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// output the code to setup the parameters and perform the actual call
|
||||
// does NOT output the code to deal with the result values!
|
||||
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
if(Register.X in sub.asmClobbers)
|
||||
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
||||
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
|
||||
if(saveX)
|
||||
asmgen.saveRegister(CpuRegister.X) // we only save X for now (required! is the eval stack pointer), screw A and Y...
|
||||
|
||||
val subName = asmgen.asmIdentifierName(stmt.target)
|
||||
val subName = asmgen.asmSymbolName(stmt.target)
|
||||
if(stmt.args.isNotEmpty()) {
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
translateFuncArguments(arg.first, arg.second, sub)
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
// via variables
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
argumentViaVariable(sub, arg.first, arg.second)
|
||||
}
|
||||
} else {
|
||||
// via registers
|
||||
if(sub.parameters.size==1) {
|
||||
// just a single parameter, no risk of clobbering registers
|
||||
argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0])
|
||||
} else {
|
||||
// multiple register arguments, risk of register clobbering.
|
||||
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
|
||||
when {
|
||||
stmt.args.all {it is AddressOf ||
|
||||
it is NumericLiteralValue ||
|
||||
it is StringLiteralValue ||
|
||||
it is ArrayLiteralValue ||
|
||||
it is IdentifierReference} -> {
|
||||
// no risk of clobbering for these simple argument types. Optimize the register loading.
|
||||
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
|
||||
argumentViaRegister(sub, arg.first, arg.second)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Risk of clobbering due to complex expression args. Work via the stack.
|
||||
registerArgsViaStackEvaluation(stmt, sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
asmgen.out(" jsr $subName")
|
||||
|
||||
if(Register.X in sub.asmClobbers)
|
||||
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
|
||||
if(saveX)
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
}
|
||||
|
||||
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
|
||||
val sourceIDt = value.inferType(program)
|
||||
if(!sourceIDt.isKnown)
|
||||
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
|
||||
// this is called when one or more of the arguments are 'complex' and
|
||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||
for (arg in stmt.args.reversed())
|
||||
asmgen.translateExpression(arg)
|
||||
for (regparam in sub.asmParameterRegisters) {
|
||||
when {
|
||||
regparam.statusflag==Statusflag.Pc -> {
|
||||
asmgen.out("""
|
||||
inx
|
||||
pha
|
||||
lda P8ESTACK_LO,x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+ pla""")
|
||||
}
|
||||
regparam.statusflag!=null -> {
|
||||
throw AssemblyError("can only use Carry as status flag parameter")
|
||||
}
|
||||
regparam.registerOrPair!=null -> {
|
||||
val tgt = AsmAssignTarget.fromRegisters(regparam.registerOrPair, program, asmgen)
|
||||
val source = AsmAssignSource(SourceStorageKind.STACK, program, tgt.datatype)
|
||||
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
|
||||
asmgen.translateNormalAssignment(asgn)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("arg type unknown")
|
||||
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
|
||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
if(sub.asmParameterRegisters.isEmpty()) {
|
||||
// pass parameter via a variable
|
||||
val paramVar = parameter.value
|
||||
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
|
||||
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
|
||||
target.linkParents(value.parent)
|
||||
when (value) {
|
||||
is NumericLiteralValue -> {
|
||||
// optimize when the argument is a constant literal
|
||||
when(parameter.value.type) {
|
||||
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
|
||||
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
||||
else -> throw AssemblyError("weird parameter datatype")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
// optimize when the argument is a variable
|
||||
when (parameter.value.type) {
|
||||
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
|
||||
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
|
||||
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
|
||||
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
|
||||
else -> throw AssemblyError("weird parameter datatype")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
asmgen.assignFromRegister(target, value.register)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
when(value.addressExpression) {
|
||||
|
||||
val scopedParamVar = (sub.scopedname+"."+parameter.value.name).split(".")
|
||||
val identifier = IdentifierReference(scopedParamVar, sub.position)
|
||||
identifier.linkParents(value.parent)
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, variable = identifier)
|
||||
val source = AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(tgt)
|
||||
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
|
||||
asmgen.translateNormalAssignment(asgn)
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
|
||||
// pass argument via a register parameter
|
||||
val valueIDt = value.inferType(program)
|
||||
if(!valueIDt.isKnown)
|
||||
throw AssemblyError("arg type unknown")
|
||||
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
||||
val statusflag = paramRegister.statusflag
|
||||
val register = paramRegister.registerOrPair
|
||||
val stack = paramRegister.stack
|
||||
when {
|
||||
stack -> {
|
||||
// push arg onto the stack
|
||||
// note: argument order is reversed (first argument will be deepest on the stack)
|
||||
asmgen.translateExpression(value)
|
||||
}
|
||||
statusflag!=null -> {
|
||||
if (statusflag == Statusflag.Pc) {
|
||||
// this param needs to be set last, right before the jsr
|
||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||
when(value) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
||||
asmgen.assignFromMemoryByte(target, address, null)
|
||||
val carrySet = value.number.toInt() != 0
|
||||
asmgen.out(if(carrySet) " sec" else " clc")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value.addressExpression)
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
|
||||
asmgen.assignFromRegister(target, Register.A)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.assignFromEvalResult(target)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// pass parameter via a register parameter
|
||||
val paramRegister = sub.asmParameterRegisters[parameter.index]
|
||||
val statusflag = paramRegister.statusflag
|
||||
val register = paramRegister.registerOrPair
|
||||
val stack = paramRegister.stack
|
||||
when {
|
||||
stack -> {
|
||||
// push arg onto the stack
|
||||
// note: argument order is reversed (first argument will be deepest on the stack)
|
||||
asmgen.translateExpression(value)
|
||||
}
|
||||
statusflag!=null -> {
|
||||
if (statusflag == Statusflag.Pc) {
|
||||
// this param needs to be set last, right before the jsr
|
||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||
when(value) {
|
||||
is NumericLiteralValue -> {
|
||||
val carrySet = value.number.toInt() != 0
|
||||
asmgen.out(if(carrySet) " sec" else " clc")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.asmIdentifierName(value)
|
||||
asmgen.out("""
|
||||
val sourceName = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
pha
|
||||
lda $sourceName
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+
|
||||
+ pla
|
||||
""")
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
when(value.register) {
|
||||
Register.A -> asmgen.out(" cmp #0")
|
||||
Register.X -> asmgen.out(" txa")
|
||||
Register.Y -> asmgen.out(" tya")
|
||||
}
|
||||
asmgen.out("""
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+
|
||||
""")
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda $ESTACK_LO_HEX,x
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
asmgen.out("""
|
||||
inx
|
||||
pha
|
||||
lda P8ESTACK_LO,x
|
||||
beq +
|
||||
sec
|
||||
bcs ++
|
||||
+ clc
|
||||
+
|
||||
+ pla
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
else throw AssemblyError("can only use Carry as status flag parameter")
|
||||
}
|
||||
register!=null && register.name.length==1 -> {
|
||||
when (value) {
|
||||
is NumericLiteralValue -> {
|
||||
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
||||
target.linkParents(value.parent)
|
||||
asmgen.assignFromByteConstant(target, value.number.toShort())
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
|
||||
target.linkParents(value.parent)
|
||||
asmgen.assignFromByteVariable(target, value)
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
when(register) {
|
||||
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
|
||||
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
|
||||
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
|
||||
else -> throw AssemblyError("cannot assign to register pair")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
register!=null && register.name.length==2 -> {
|
||||
// register pair as a 16-bit value (only possible for subroutine parameters)
|
||||
when (value) {
|
||||
is NumericLiteralValue -> {
|
||||
// optimize when the argument is a constant literal
|
||||
val hex = value.number.toHex()
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
is AddressOf -> {
|
||||
// optimize when the argument is an address of something
|
||||
val sourceName = asmgen.asmIdentifierName(value.identifier)
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.asmIdentifierName(value)
|
||||
when (register) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value)
|
||||
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
|
||||
throw AssemblyError("can't use X register here - use a variable")
|
||||
else if (register == RegisterOrPair.AY)
|
||||
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
|
||||
}
|
||||
}
|
||||
else throw AssemblyError("can only use Carry as status flag parameter")
|
||||
}
|
||||
else -> {
|
||||
// via register or register pair
|
||||
val target = AsmAssignTarget.fromRegisters(register!!, program, asmgen)
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
val addr = AddressOf(value as IdentifierReference, Position.DUMMY)
|
||||
AsmAssignSource.fromAstSource(addr, program).adjustDataTypeToTarget(target)
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(target)
|
||||
}
|
||||
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
|
||||
if(argType isAssignableTo paramType)
|
||||
return true
|
||||
if(argType in ByteDatatypes && paramType in ByteDatatypes)
|
||||
return true
|
||||
if(argType in WordDatatypes && paramType in WordDatatypes)
|
||||
return true
|
||||
|
||||
// we have a special rule for some types.
|
||||
// strings are assignable to UWORD, for example, and vice versa
|
||||
|
@ -4,10 +4,8 @@ import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteralValue
|
||||
import prog8.ast.expressions.RegisterExpr
|
||||
import prog8.ast.statements.PostIncrDecr
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
|
||||
import prog8.compiler.toHex
|
||||
|
||||
|
||||
@ -17,28 +15,10 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
val targetIdent = stmt.target.identifier
|
||||
val targetMemory = stmt.target.memoryAddress
|
||||
val targetArrayIdx = stmt.target.arrayindexed
|
||||
val targetRegister = stmt.target.register
|
||||
when {
|
||||
targetRegister!=null -> {
|
||||
when(targetRegister) {
|
||||
Register.A -> {
|
||||
if(incr)
|
||||
asmgen.out(" clc | adc #1 ")
|
||||
else
|
||||
asmgen.out(" sec | sbc #1 ")
|
||||
}
|
||||
Register.X -> {
|
||||
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
|
||||
}
|
||||
Register.Y -> {
|
||||
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
|
||||
}
|
||||
}
|
||||
}
|
||||
targetIdent!=null -> {
|
||||
val what = asmgen.asmIdentifierName(targetIdent)
|
||||
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)
|
||||
when (dt) {
|
||||
val what = asmgen.asmVariableName(targetIdent)
|
||||
when (stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
@ -65,87 +45,91 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
asmgen.out(if(incr) " inc $what" else " dec $what")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val what = asmgen.asmIdentifierName(addressExpr)
|
||||
val what = asmgen.asmVariableName(addressExpr)
|
||||
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
|
||||
if(incr)
|
||||
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||
else
|
||||
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||
}
|
||||
else -> throw AssemblyError("weird target type $targetMemory")
|
||||
else -> {
|
||||
asmgen.translateExpression(addressExpr)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta (+) + 1
|
||||
lda P8ESTACK_HI,x
|
||||
sta (+) + 2
|
||||
""")
|
||||
if(incr)
|
||||
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||
else
|
||||
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||
}
|
||||
}
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val index = targetArrayIdx.arrayspec.index
|
||||
val what = asmgen.asmIdentifierName(targetArrayIdx.identifier)
|
||||
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
val elementDt = ArrayElementTypes.getValue(arrayDt)
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.identifier)
|
||||
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when(index) {
|
||||
is NumericLiteralValue -> {
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue")
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+")
|
||||
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $what+$indexValue
|
||||
lda $asmArrayvarname+$indexValue
|
||||
bne +
|
||||
dec $what+$indexValue+1
|
||||
+ dec $what+$indexValue
|
||||
dec $asmArrayvarname+$indexValue+1
|
||||
+ dec $asmArrayvarname+$indexValue
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out(" lda #<$what+$indexValue | ldy #>$what+$indexValue")
|
||||
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
|
||||
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("need numeric type")
|
||||
}
|
||||
}
|
||||
is RegisterExpr -> {
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateArrayIndexIntoA(targetArrayIdx)
|
||||
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
|
||||
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
|
||||
asmgen.saveRegister(CpuRegister.X)
|
||||
asmgen.out(" tax")
|
||||
when(elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $asmArrayvarname,x
|
||||
bne +
|
||||
dec $asmArrayvarname+1,x
|
||||
+ dec $asmArrayvarname
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
ldy #>$asmArrayvarname
|
||||
clc
|
||||
adc #<$asmArrayvarname
|
||||
bcc +
|
||||
iny
|
||||
+ jsr c64flt.inc_var_f""")
|
||||
}
|
||||
else -> throw AssemblyError("weird array elt dt")
|
||||
}
|
||||
asmgen.restoreRegister(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird target type ${stmt.target}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
|
||||
asmgen.out(" stx ${C64Zeropage.SCRATCH_REG_X} | tax")
|
||||
when(arrayDt) {
|
||||
DataType.STR,
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
if(incr)
|
||||
asmgen.out(" inc $arrayVarName,x | bne + | inc $arrayVarName+1,x |+")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda $arrayVarName,x
|
||||
bne +
|
||||
dec $arrayVarName+1,x
|
||||
+ dec $arrayVarName
|
||||
""")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
asmgen.out(" lda #<$arrayVarName | ldy #>$arrayVarName")
|
||||
asmgen.out(if(incr) " jsr c64flt.inc_indexed_var_f" else " jsr c64flt.dec_indexed_var_f")
|
||||
}
|
||||
else -> throw AssemblyError("weird array dt")
|
||||
}
|
||||
asmgen.out(" ldx ${C64Zeropage.SCRATCH_REG_X}")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,164 @@
|
||||
package prog8.compiler.target.c64.codegen.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
|
||||
|
||||
internal enum class TargetStorageKind {
|
||||
VARIABLE,
|
||||
ARRAY,
|
||||
MEMORY,
|
||||
REGISTER,
|
||||
STACK
|
||||
}
|
||||
|
||||
internal enum class SourceStorageKind {
|
||||
LITERALNUMBER,
|
||||
VARIABLE,
|
||||
ARRAY,
|
||||
MEMORY,
|
||||
REGISTER,
|
||||
STACK, // value is already present on stack
|
||||
EXPRESSION, // expression in ast-form, still to be evaluated
|
||||
}
|
||||
|
||||
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
program: Program,
|
||||
asmgen: AsmGen,
|
||||
val datatype: DataType,
|
||||
val variable: IdentifierReference? = null,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryWrite? = null,
|
||||
val register: RegisterOrPair? = null,
|
||||
val origAstTarget: AssignTarget? = null
|
||||
)
|
||||
{
|
||||
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 asmVarname by lazy {
|
||||
if(variable!=null)
|
||||
asmgen.asmVariableName(variable)
|
||||
else
|
||||
asmgen.asmVariableName(array!!.identifier)
|
||||
}
|
||||
|
||||
lateinit var origAssign: AsmAssignment
|
||||
|
||||
init {
|
||||
if(variable!=null && vardecl.type == VarDeclType.CONST)
|
||||
throw AssemblyError("can't assign to a constant")
|
||||
if(register!=null && datatype !in IntegerDatatypes)
|
||||
throw AssemblyError("register must be integer type")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
|
||||
val dt = inferType(program, assign).typeOrElse(DataType.STRUCT)
|
||||
when {
|
||||
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, variable=identifier, origAstTarget = this)
|
||||
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, memory = memoryAddress, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
|
||||
fun fromRegisters(registers: RegisterOrPair, program: Program, asmgen: AsmGen): AsmAssignTarget =
|
||||
when(registers) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, register = registers)
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, register = registers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
private val program: Program,
|
||||
val datatype: DataType,
|
||||
val variable: IdentifierReference? = null,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryRead? = null,
|
||||
val register: CpuRegister? = null,
|
||||
val number: NumericLiteralValue? = null,
|
||||
val expression: Expression? = null
|
||||
)
|
||||
{
|
||||
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)!! }
|
||||
|
||||
companion object {
|
||||
fun fromAstSource(value: Expression, program: Program): AsmAssignSource {
|
||||
val cv = value.constValue(program)
|
||||
if(cv!=null)
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, cv.type, number = cv)
|
||||
|
||||
return when(value) {
|
||||
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, value.type, number = cv)
|
||||
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is IdentifierReference -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, dt, variable = value)
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
AsmAssignSource(SourceStorageKind.MEMORY, program, DataType.UBYTE, memory = value)
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, dt, array = value)
|
||||
}
|
||||
else -> {
|
||||
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, dt, expression = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getAstValue(): Expression = when(kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> number!!
|
||||
SourceStorageKind.VARIABLE -> variable!!
|
||||
SourceStorageKind.ARRAY -> array!!
|
||||
SourceStorageKind.MEMORY -> memory!!
|
||||
SourceStorageKind.EXPRESSION -> expression!!
|
||||
SourceStorageKind.REGISTER -> throw AssemblyError("cannot get a register source as Ast node")
|
||||
SourceStorageKind.STACK -> throw AssemblyError("cannot get a stack source as Ast node")
|
||||
}
|
||||
|
||||
fun withAdjustedDt(newType: DataType) =
|
||||
AsmAssignSource(kind, program, newType, variable, array, memory, register, number, expression)
|
||||
|
||||
fun adjustDataTypeToTarget(target: AsmAssignTarget): AsmAssignSource {
|
||||
// allow some signed/unsigned relaxations
|
||||
if(target.datatype!=datatype) {
|
||||
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
|
||||
return withAdjustedDt(target.datatype)
|
||||
} else if(target.datatype in WordDatatypes && datatype in WordDatatypes) {
|
||||
return withAdjustedDt(target.datatype)
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
internal class AsmAssignment(val source: AsmAssignSource,
|
||||
val target: AsmAssignTarget,
|
||||
val isAugmentable: Boolean,
|
||||
val position: Position) {
|
||||
|
||||
init {
|
||||
require(source.datatype==target.datatype) {"source and target datatype must be identical"}
|
||||
}
|
||||
}
|
@ -0,0 +1,966 @@
|
||||
package prog8.compiler.target.c64.codegen.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.AssemblyError
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.c64.codegen.AsmGen
|
||||
import prog8.compiler.toHex
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
|
||||
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
|
||||
|
||||
fun translate(assignment: Assignment) {
|
||||
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
|
||||
val source = AsmAssignSource.fromAstSource(assignment.value, program).adjustDataTypeToTarget(target)
|
||||
|
||||
val assign = AsmAssignment(source, target, assignment.isAugmentable, assignment.position)
|
||||
target.origAssign = assign
|
||||
|
||||
if(assign.isAugmentable)
|
||||
augmentableAsmGen.translate(assign)
|
||||
else
|
||||
translateNormalAssignment(assign)
|
||||
}
|
||||
|
||||
fun translateNormalAssignment(assign: AsmAssignment) {
|
||||
when(assign.source.kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> {
|
||||
// simple case: assign a constant number
|
||||
val num = assign.source.number!!.number
|
||||
when (assign.source.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toShort())
|
||||
DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt())
|
||||
DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
|
||||
else -> throw AssemblyError("weird numval type")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
// simple case: assign from another variable
|
||||
val variable = assign.source.variable!!
|
||||
when (assign.source.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
|
||||
DataType.UWORD, DataType.WORD -> assignVariableWord(assign.target, variable)
|
||||
DataType.FLOAT -> assignVariableFloat(assign.target, variable)
|
||||
in PassByReferenceDatatypes -> assignAddressOf(assign.target, variable)
|
||||
else -> throw AssemblyError("unsupported assignment target type ${assign.target.datatype}")
|
||||
}
|
||||
}
|
||||
SourceStorageKind.ARRAY -> {
|
||||
val value = assign.source.array!!
|
||||
val elementDt = assign.source.datatype
|
||||
val index = value.arrayspec.index
|
||||
val arrayVarName = asmgen.asmVariableName(value.identifier)
|
||||
if (index is NumericLiteralValue) {
|
||||
// constant array index value
|
||||
val indexValue = index.number.toInt() * elementDt.memorySize()
|
||||
when (elementDt) {
|
||||
in ByteDatatypes ->
|
||||
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
|
||||
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")
|
||||
else ->
|
||||
throw AssemblyError("weird array type")
|
||||
}
|
||||
} else {
|
||||
when (elementDt) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.Y)
|
||||
asmgen.out(" lda $arrayVarName,y | sta P8ESTACK_LO,x | lda $arrayVarName+1,y | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(value, elementDt, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>$arrayVarName
|
||||
clc
|
||||
adc #<$arrayVarName
|
||||
bcc +
|
||||
iny
|
||||
+ jsr c64flt.push_float""")
|
||||
}
|
||||
else ->
|
||||
throw AssemblyError("weird array elt type")
|
||||
}
|
||||
}
|
||||
assignStackValue(assign.target)
|
||||
}
|
||||
SourceStorageKind.MEMORY -> {
|
||||
val value = assign.source.memory!!
|
||||
when (value.addressExpression) {
|
||||
is NumericLiteralValue -> {
|
||||
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
|
||||
assignMemoryByte(assign.target, address, null)
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
assignMemoryByte(assign.target, null, value.addressExpression as IdentifierReference)
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(value.addressExpression)
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | inx")
|
||||
assignRegisterByte(assign.target, CpuRegister.A)
|
||||
}
|
||||
}
|
||||
}
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
val value = assign.source.expression!!
|
||||
when(value) {
|
||||
is AddressOf -> assignAddressOf(assign.target, value.identifier)
|
||||
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
|
||||
is IdentifierReference -> throw AssemblyError("source kind should have been variable")
|
||||
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
|
||||
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
|
||||
// is TypecastExpression -> {
|
||||
// if(assign.target.kind == TargetStorageKind.STACK) {
|
||||
// asmgen.translateExpression(value)
|
||||
// assignStackValue(assign.target)
|
||||
// } else {
|
||||
// println("!!!!TYPECAST to ${assign.target.kind} $value")
|
||||
// // TODO maybe we can do the typecast on the target directly instead of on the stack?
|
||||
// asmgen.translateExpression(value)
|
||||
// assignStackValue(assign.target)
|
||||
// }
|
||||
// }
|
||||
// is FunctionCall -> {
|
||||
// if (assign.target.kind == TargetStorageKind.STACK) {
|
||||
// asmgen.translateExpression(value)
|
||||
// assignStackValue(assign.target)
|
||||
// } else {
|
||||
// val functionName = value.target.nameInSource.last()
|
||||
// val builtinFunc = BuiltinFunctions[functionName]
|
||||
// if (builtinFunc != null) {
|
||||
// println("!!!!BUILTIN-FUNCCALL target=${assign.target.kind} $value") // TODO optimize certain functions?
|
||||
// }
|
||||
// asmgen.translateExpression(value)
|
||||
// assignStackValue(assign.target)
|
||||
// }
|
||||
// }
|
||||
else -> {
|
||||
// everything else just evaluate via the stack.
|
||||
asmgen.translateExpression(value)
|
||||
assignStackValue(assign.target)
|
||||
}
|
||||
}
|
||||
}
|
||||
SourceStorageKind.REGISTER -> {
|
||||
assignRegisterByte(assign.target, assign.source.register!!)
|
||||
}
|
||||
SourceStorageKind.STACK -> {
|
||||
assignStackValue(assign.target)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignStackValue(target: AsmAssignTarget) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
when (target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta ${target.asmVarname}
|
||||
lda P8ESTACK_HI,x
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}
|
||||
ldy #>${target.asmVarname}
|
||||
jsr c64flt.pop_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
asmgen.out(" inx")
|
||||
storeByteViaRegisterAInMemoryAddress("P8ESTACK_LO,x", target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = target.array!!.arrayspec.index
|
||||
when {
|
||||
target.constArrayIndexValue!=null -> {
|
||||
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta ${target.asmVarname}+$scaledIdx
|
||||
lda P8ESTACK_HI,x
|
||||
sta ${target.asmVarname}+$scaledIdx+1
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
jsr c64flt.pop_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
}
|
||||
}
|
||||
index is IdentifierReference -> {
|
||||
when(target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname},y")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta ${target.asmVarname},y
|
||||
lda P8ESTACK_HI,x
|
||||
sta ${target.asmVarname}+1,y
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #>${target.asmVarname}
|
||||
clc
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ jsr c64flt.pop_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when (target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||
RegisterOrPair.X -> throw AssemblyError("can't use X here")
|
||||
RegisterOrPair.Y -> asmgen.out(" inx | ldy P8ESTACK_LO,x")
|
||||
else -> throw AssemblyError("can't assign byte to register pair word")
|
||||
}
|
||||
}
|
||||
DataType.UWORD, DataType.WORD, in PassByReferenceDatatypes -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.AX -> throw AssemblyError("can't use X here")
|
||||
RegisterOrPair.AY-> asmgen.out(" inx | lda P8ESTACK_LO,x | ldy P8ESTACK_HI,x")
|
||||
RegisterOrPair.XY-> throw AssemblyError("can't use X here")
|
||||
else -> throw AssemblyError("can't assign word to single byte register")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {}
|
||||
}
|
||||
}
|
||||
|
||||
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("."))
|
||||
}
|
||||
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
sta ${target.asmVarname}
|
||||
sty ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
throw AssemblyError("no asm gen for assign address $sourceName to memory word $target")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
throw AssemblyError("no asm gen for assign address $sourceName to array ${target.asmVarname}")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
|
||||
else -> throw AssemblyError("can't load address in a single 8-bit register")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
val srcname = asmgen.asmVariableName(name)
|
||||
asmgen.out("""
|
||||
lda #<$srcname
|
||||
sta P8ESTACK_LO,x
|
||||
lda #>$srcname
|
||||
sta P8ESTACK_HI,x
|
||||
dex""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableWord(target: AsmAssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(variable)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
ldy $sourceName+1
|
||||
sta ${target.asmVarname}
|
||||
sty ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
throw AssemblyError("no asm gen for assign wordvar $sourceName to memory ${target.memory}")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = target.array!!.arrayspec.index
|
||||
when {
|
||||
target.constArrayIndexValue!=null -> {
|
||||
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||
when(target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${target.asmVarname}+$scaledIdx
|
||||
lda $sourceName+1
|
||||
sta ${target.asmVarname}+$scaledIdx+1
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.out("""
|
||||
lda #<$sourceName
|
||||
ldy #>$sourceName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #<${target.asmVarname}+$scaledIdx
|
||||
ldy #>${target.asmVarname}+$scaledIdx
|
||||
jsr c64flt.copy_float
|
||||
""")
|
||||
}
|
||||
else -> throw AssemblyError("weird target variable type ${target.datatype}")
|
||||
}
|
||||
}
|
||||
index is IdentifierReference -> {
|
||||
when(target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
|
||||
}
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${target.asmVarname},y
|
||||
lda $sourceName+1
|
||||
sta ${target.asmVarname}+1,y
|
||||
""")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
|
||||
asmgen.out("""
|
||||
ldy #<$sourceName
|
||||
sty P8ZP_SCRATCH_W1
|
||||
ldy #>$sourceName
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #>${target.asmVarname}
|
||||
clc
|
||||
adc #<${target.asmVarname}
|
||||
bcc +
|
||||
iny
|
||||
+ jsr c64flt.copy_float""")
|
||||
}
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | lda $sourceName+1 | sta P8ESTACK_HI,x | dex")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
|
||||
else -> throw AssemblyError("can't load word in a single 8-bit register")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.out("""
|
||||
lda #$sourceName
|
||||
sta P8ESTACK_LO,x
|
||||
lda #$sourceName+1
|
||||
sta P8ESTACK_HI,x
|
||||
dex""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableFloat(target: AsmAssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(variable)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${target.asmVarname}
|
||||
lda $sourceName+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $sourceName+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $sourceName+3
|
||||
sta ${target.asmVarname}+3
|
||||
lda $sourceName+4
|
||||
sta ${target.asmVarname}+4
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// TODO optimize this, but the situation doesn't occur very often
|
||||
// if(target.constArrayIndexValue!=null) {
|
||||
// TODO("const index ${target.constArrayIndexValue}")
|
||||
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||
// }
|
||||
val index = target.array!!.arrayspec.index
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname} | jsr c64flt.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")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVariableByte(target: AsmAssignTarget, variable: IdentifierReference) {
|
||||
val sourceName = asmgen.asmVariableName(variable)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda $sourceName
|
||||
sta ${target.asmVarname}
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = target.array!!.arrayspec.index
|
||||
when {
|
||||
target.constArrayIndexValue!=null -> {
|
||||
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
|
||||
}
|
||||
index is IdentifierReference -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
|
||||
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
|
||||
}
|
||||
else -> {
|
||||
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out(" inx | lda P8ESTACK_LO,x")
|
||||
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda $sourceName")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx $sourceName")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName")
|
||||
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx #0")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy #0")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.out("""
|
||||
lda #$sourceName
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
|
||||
require(target.datatype in ByteDatatypes)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out(" st${register.name.toLowerCase()} ${target.asmVarname}")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
storeRegisterInMemoryAddress(register, target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = target.array!!.arrayspec.index
|
||||
when (index) {
|
||||
is NumericLiteralValue -> {
|
||||
val memindex = index.number.toInt()
|
||||
when (register) {
|
||||
CpuRegister.A -> asmgen.out(" sta ${target.asmVarname}+$memindex")
|
||||
CpuRegister.X -> asmgen.out(" stx ${target.asmVarname}+$memindex")
|
||||
CpuRegister.Y -> asmgen.out(" sty ${target.asmVarname}+$memindex")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> asmgen.out(" txa")
|
||||
CpuRegister.Y -> asmgen.out(" tya")
|
||||
}
|
||||
asmgen.out(" ldy ${asmgen.asmVariableName(index)} | sta ${target.asmVarname},y")
|
||||
}
|
||||
else -> {
|
||||
asmgen.saveRegister(register)
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.restoreRegister(register)
|
||||
when (register) {
|
||||
CpuRegister.A -> asmgen.out(" sta P8ZP_SCRATCH_B1")
|
||||
CpuRegister.X -> asmgen.out(" stx P8ZP_SCRATCH_B1")
|
||||
CpuRegister.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1")
|
||||
}
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
tay
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta ${target.asmVarname},y
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(register) {
|
||||
CpuRegister.A -> when(target.register!!) {
|
||||
RegisterOrPair.A -> {}
|
||||
RegisterOrPair.X -> { asmgen.out(" tax") }
|
||||
RegisterOrPair.Y -> { asmgen.out(" tay") }
|
||||
else -> throw AssemblyError("attempt to assign byte to register pair word")
|
||||
}
|
||||
CpuRegister.X -> when(target.register!!) {
|
||||
RegisterOrPair.A -> { asmgen.out(" txa") }
|
||||
RegisterOrPair.X -> { }
|
||||
RegisterOrPair.Y -> { asmgen.out(" txy") }
|
||||
else -> throw AssemblyError("attempt to assign byte to register pair word")
|
||||
}
|
||||
CpuRegister.Y -> when(target.register!!) {
|
||||
RegisterOrPair.A -> { asmgen.out(" tya") }
|
||||
RegisterOrPair.X -> { asmgen.out(" tyx") }
|
||||
RegisterOrPair.Y -> { }
|
||||
else -> throw AssemblyError("attempt to assign byte to register pair word")
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
when(register) {
|
||||
CpuRegister.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
CpuRegister.X -> throw AssemblyError("can't use X here")
|
||||
CpuRegister.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if (word ushr 8 == word and 255) {
|
||||
// lsb=msb
|
||||
asmgen.out("""
|
||||
lda #${(word and 255).toHex()}
|
||||
sta ${target.asmVarname}
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda #<${word.toHex()}
|
||||
ldy #>${word.toHex()}
|
||||
sta ${target.asmVarname}
|
||||
sty ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// TODO optimize this, but the situation doesn't occur very often
|
||||
// if(target.constArrayIndexValue!=null) {
|
||||
// TODO("const index ${target.constArrayIndexValue}")
|
||||
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||
// }
|
||||
val index = target.array!!.arrayspec.index
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
asl a
|
||||
tay
|
||||
lda #<${word.toHex()}
|
||||
sta ${target.asmVarname},y
|
||||
lda #>${word.toHex()}
|
||||
sta ${target.asmVarname}+1,y
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.AX -> asmgen.out(" lda #<${word.toHex()} | ldx #>${word.toHex()}")
|
||||
RegisterOrPair.AY -> asmgen.out(" lda #<${word.toHex()} | ldy #>${word.toHex()}")
|
||||
RegisterOrPair.XY -> asmgen.out(" ldx #<${word.toHex()} | ldy #>${word.toHex()}")
|
||||
else -> throw AssemblyError("can't assign word to single byte register")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.out("""
|
||||
lda #<${word.toHex()}
|
||||
sta P8ESTACK_LO,x
|
||||
lda #>${word.toHex()}
|
||||
sta P8ESTACK_HI,x
|
||||
dex""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname} ")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
storeByteViaRegisterAInMemoryAddress("#${byte.toHex()}", target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
val index = target.array!!.arrayspec.index
|
||||
when {
|
||||
target.constArrayIndexValue!=null -> {
|
||||
val indexValue = target.constArrayIndexValue!!
|
||||
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname}+$indexValue")
|
||||
}
|
||||
index is IdentifierReference -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
|
||||
asmgen.out(" lda #<${byte.toHex()} | sta ${target.asmVarname},y")
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
lda #${byte.toHex()}
|
||||
sta ${target.asmVarname},y
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> when(target.register!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda #${byte.toHex()}")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx #${byte.toHex()}")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy #${byte.toHex()}")
|
||||
else -> throw AssemblyError("can't assign byte to word register apir")
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.out("""
|
||||
lda #${byte.toHex()}
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignConstantFloat(target: AsmAssignTarget, float: Double) {
|
||||
if (float == 0.0) {
|
||||
// optimized case for float zero
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if(CompilationTarget.machine.cpu == CpuType.CPU65c02)
|
||||
asmgen.out("""
|
||||
stz ${target.asmVarname}
|
||||
stz ${target.asmVarname}+1
|
||||
stz ${target.asmVarname}+2
|
||||
stz ${target.asmVarname}+3
|
||||
stz ${target.asmVarname}+4
|
||||
""")
|
||||
else
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta ${target.asmVarname}
|
||||
sta ${target.asmVarname}+1
|
||||
sta ${target.asmVarname}+2
|
||||
sta ${target.asmVarname}+3
|
||||
sta ${target.asmVarname}+4
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// TODO optimize this, but the situation doesn't occur very often
|
||||
// if(target.constArrayIndexValue!=null) {
|
||||
// TODO("const index ${target.constArrayIndexValue}")
|
||||
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||
// }
|
||||
val index = target.array!!.arrayspec.index
|
||||
if (index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * DataType.FLOAT.memorySize()
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sta ${target.asmVarname}+$indexValue
|
||||
sta ${target.asmVarname}+$indexValue+1
|
||||
sta ${target.asmVarname}+$indexValue+2
|
||||
sta ${target.asmVarname}+$indexValue+3
|
||||
sta ${target.asmVarname}+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
lda #<${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${target.asmVarname}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
jsr c64flt.set_0_array_float
|
||||
""")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
|
||||
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")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// non-zero value
|
||||
val constFloat = asmgen.getFloatAsmConst(float)
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta ${target.asmVarname}
|
||||
lda $constFloat+1
|
||||
sta ${target.asmVarname}+1
|
||||
lda $constFloat+2
|
||||
sta ${target.asmVarname}+2
|
||||
lda $constFloat+3
|
||||
sta ${target.asmVarname}+3
|
||||
lda $constFloat+4
|
||||
sta ${target.asmVarname}+4
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
// TODO optimize this, but the situation doesn't occur very often
|
||||
// if(target.constArrayIndexValue!=null) {
|
||||
// TODO("const index ${target.constArrayIndexValue}")
|
||||
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
|
||||
// TODO("array[var] ${target.constArrayIndexValue}")
|
||||
// }
|
||||
val index = target.array!!.arrayspec.index
|
||||
val arrayVarName = target.asmVarname
|
||||
if (index is NumericLiteralValue) {
|
||||
val indexValue = index.number.toInt() * DataType.FLOAT.memorySize()
|
||||
asmgen.out("""
|
||||
lda $constFloat
|
||||
sta $arrayVarName+$indexValue
|
||||
lda $constFloat+1
|
||||
sta $arrayVarName+$indexValue+1
|
||||
lda $constFloat+2
|
||||
sta $arrayVarName+$indexValue+2
|
||||
lda $constFloat+3
|
||||
sta $arrayVarName+$indexValue+3
|
||||
lda $constFloat+4
|
||||
sta $arrayVarName+$indexValue+4
|
||||
""")
|
||||
} else {
|
||||
asmgen.translateExpression(index)
|
||||
asmgen.out("""
|
||||
lda #<${constFloat}
|
||||
sta P8ZP_SCRATCH_W1
|
||||
lda #>${constFloat}
|
||||
sta P8ZP_SCRATCH_W1+1
|
||||
lda #<${arrayVarName}
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda #>${arrayVarName}
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
jsr c64flt.set_array_float
|
||||
""")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignMemoryByte(target: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
|
||||
if (address != null) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda ${address.toHex()}
|
||||
sta ${target.asmVarname}
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
storeByteViaRegisterAInMemoryAddress(address.toHex(), target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
throw AssemblyError("no asm gen for assign memory byte at $address to array ${target.asmVarname}")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> when(target.register!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" lda ${address.toHex()}")
|
||||
RegisterOrPair.X -> asmgen.out(" ldx ${address.toHex()}")
|
||||
RegisterOrPair.Y -> asmgen.out(" ldy ${address.toHex()}")
|
||||
else -> throw AssemblyError("can't assign byte to word register apir")
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.out("""
|
||||
lda ${address.toHex()}
|
||||
sta P8ESTACK_LO,x
|
||||
dex""")
|
||||
}
|
||||
}
|
||||
} else if (identifier != null) {
|
||||
when(target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.loadByteFromPointerIntoA(identifier)
|
||||
asmgen.out(" sta ${target.asmVarname}")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
val sourceName = asmgen.asmVariableName(identifier)
|
||||
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!)
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
throw AssemblyError("no asm gen for assign memory byte $identifier to array ${target.asmVarname} ")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
asmgen.loadByteFromPointerIntoA(identifier)
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.A -> {}
|
||||
RegisterOrPair.X -> asmgen.out(" tax")
|
||||
RegisterOrPair.Y -> asmgen.out(" tay")
|
||||
else -> throw AssemblyError("can't assign byte to word register apir")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
asmgen.loadByteFromPointerIntoA(identifier)
|
||||
asmgen.out(" sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeByteViaRegisterAInMemoryAddress(ldaInstructionArg: String, memoryAddress: DirectMemoryWrite) {
|
||||
val addressExpr = memoryAddress.addressExpression
|
||||
val addressLv = addressExpr as? NumericLiteralValue
|
||||
when {
|
||||
addressLv != null -> {
|
||||
asmgen.out(" lda $ldaInstructionArg | sta ${addressLv.number.toHex()}")
|
||||
}
|
||||
addressExpr is IdentifierReference -> {
|
||||
asmgen.storeByteIntoPointer(addressExpr, ldaInstructionArg)
|
||||
}
|
||||
else -> {
|
||||
asmgen.translateExpression(addressExpr)
|
||||
asmgen.out("""
|
||||
inx
|
||||
lda P8ESTACK_LO,x
|
||||
sta P8ZP_SCRATCH_W2
|
||||
lda P8ESTACK_HI,x
|
||||
sta P8ZP_SCRATCH_W2+1
|
||||
lda $ldaInstructionArg
|
||||
ldy #0
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun storeRegisterInMemoryAddress(register: CpuRegister, memoryAddress: DirectMemoryWrite) {
|
||||
// this is optimized for register A.
|
||||
val addressExpr = memoryAddress.addressExpression
|
||||
val addressLv = addressExpr as? NumericLiteralValue
|
||||
val registerName = register.name.toLowerCase()
|
||||
when {
|
||||
addressLv != null -> {
|
||||
asmgen.out(" st$registerName ${addressLv.number.toHex()}")
|
||||
}
|
||||
addressExpr is IdentifierReference -> {
|
||||
when (register) {
|
||||
CpuRegister.A -> {}
|
||||
CpuRegister.X -> asmgen.out(" txa")
|
||||
CpuRegister.Y -> asmgen.out(" tya")
|
||||
}
|
||||
asmgen.storeByteIntoPointer(addressExpr, null)
|
||||
}
|
||||
else -> {
|
||||
asmgen.saveRegister(register)
|
||||
asmgen.translateExpression(addressExpr)
|
||||
asmgen.restoreRegister(CpuRegister.A)
|
||||
asmgen.out("""
|
||||
inx
|
||||
ldy P8ESTACK_LO,x
|
||||
sty P8ZP_SCRATCH_W2
|
||||
ldy P8ESTACK_HI,x
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
ldy #0
|
||||
sta (P8ZP_SCRATCH_W2),y""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun popAndWriteArrayvalueWithUnscaledIndexA(elementDt: DataType, asmArrayvarname: String) {
|
||||
when (elementDt) {
|
||||
in ByteDatatypes ->
|
||||
asmgen.out(" tay | inx | lda P8ESTACK_LO,x | sta $asmArrayvarname,y")
|
||||
in WordDatatypes ->
|
||||
asmgen.out(" asl a | tay | inx | lda P8ESTACK_LO,x | sta $asmArrayvarname,y | lda P8ESTACK_HI,x | sta $asmArrayvarname+1,y")
|
||||
DataType.FLOAT ->
|
||||
// scaling * 5 is done in the subroutine that's called
|
||||
asmgen.out("""
|
||||
sta P8ESTACK_LO,x
|
||||
dex
|
||||
lda #<$asmArrayvarname
|
||||
ldy #>$asmArrayvarname
|
||||
jsr c64flt.pop_float_to_indexed_var
|
||||
""")
|
||||
else ->
|
||||
throw AssemblyError("weird array type")
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
126
compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt
Normal file
126
compiler/src/prog8/compiler/target/cx16/CX16MachineDefinition.kt
Normal file
@ -0,0 +1,126 @@
|
||||
package prog8.compiler.target.cx16
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.CpuType
|
||||
import prog8.compiler.target.IMachineDefinition
|
||||
import prog8.compiler.target.c64.C64MachineDefinition
|
||||
import prog8.parser.ModuleImporter
|
||||
import java.io.IOException
|
||||
|
||||
internal object CX16MachineDefinition: IMachineDefinition {
|
||||
|
||||
override val cpu = CpuType.CPU65c02
|
||||
|
||||
// 5-byte cbm MFLPT format limitations:
|
||||
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
|
||||
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
|
||||
override val FLOAT_MEM_SIZE = 5
|
||||
override val POINTER_MEM_SIZE = 2
|
||||
override val BASIC_LOAD_ADDRESS = 0x0801
|
||||
override val RAW_LOAD_ADDRESS = 0x8000
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
// and some heavily used string constants derived from the two values above
|
||||
override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive
|
||||
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override val initSystemProcname = "cx16.init_system"
|
||||
|
||||
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
override fun launchEmulator(programName: String) {
|
||||
for(emulator in listOf("x16emu")) {
|
||||
println("\nStarting Commander X16 emulator $emulator...")
|
||||
val cmdline = listOf(emulator, "-rom", "/usr/share/x16-rom/rom.bin", "-scale", "2",
|
||||
"-run", "-prg", programName + ".prg")
|
||||
val processb = ProcessBuilder(cmdline).inheritIO()
|
||||
val process: Process
|
||||
try {
|
||||
process=processb.start()
|
||||
} catch(x: IOException) {
|
||||
continue // try the next emulator executable
|
||||
}
|
||||
process.waitFor()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override fun initializeZeropage(compilerOptions: CompilationOptions) {
|
||||
zeropage = CX16Zeropage(compilerOptions)
|
||||
}
|
||||
|
||||
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
|
||||
override val opcodeNames = setOf("adc", "and", "asl", "bcc", "bcs",
|
||||
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
|
||||
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey",
|
||||
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
|
||||
"inc", "inx", "iny", "jmp", "jsr",
|
||||
"lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php",
|
||||
"pla", "plp", "rol", "ror", "rti", "rts", "sbc",
|
||||
"sec", "sed", "sei",
|
||||
"sta", "stx", "sty", "tax", "tay", "tsx", "txa", "txs", "tya",
|
||||
"bra", "phx", "phy", "plx", "ply", "stz", "trb", "tsb", "bbr", "bbs",
|
||||
"rmb", "smb", "stp", "wai")
|
||||
|
||||
|
||||
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
|
||||
override val SCRATCH_B1 = 0x79 // temp storage for a single byte
|
||||
override val SCRATCH_REG = 0x7a // temp storage for a register
|
||||
override val SCRATCH_REG_X = 0x7b // temp storage for register X (the evaluation stack pointer)
|
||||
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
|
||||
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'")
|
||||
|
||||
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
|
||||
|
||||
when (options.zeropage) {
|
||||
ZeropageType.FULL -> {
|
||||
free.addAll(0x22..0xff)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
}
|
||||
ZeropageType.KERNALSAFE -> {
|
||||
free.addAll(0x22..0x7f)
|
||||
free.addAll(0xa9..0xff)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
}
|
||||
ZeropageType.BASICSAFE -> {
|
||||
free.addAll(0x22..0x7f)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
|
||||
}
|
||||
ZeropageType.DONTUSE -> {
|
||||
free.clear() // don't use zeropage at all
|
||||
}
|
||||
else -> throw CompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
|
||||
}
|
||||
|
||||
require(SCRATCH_B1 !in free)
|
||||
require(SCRATCH_REG !in free)
|
||||
require(SCRATCH_REG_X !in free)
|
||||
require(SCRATCH_W1 !in free)
|
||||
require(SCRATCH_W2 !in free)
|
||||
|
||||
for (reserved in options.zpReserved)
|
||||
reserve(reserved)
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ package prog8.functions
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.StructDecl
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.compiler.CompilerException
|
||||
import kotlin.math.*
|
||||
|
||||
@ -25,8 +27,6 @@ val BuiltinFunctions = mapOf(
|
||||
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"lsl" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
|
||||
"lsr" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
|
||||
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
// these few have a return value depending on the argument(s):
|
||||
@ -35,6 +35,7 @@ val BuiltinFunctions = mapOf(
|
||||
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
|
||||
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
|
||||
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
|
||||
// normal functions follow:
|
||||
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
|
||||
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
|
||||
@ -62,7 +63,7 @@ val BuiltinFunctions = mapOf(
|
||||
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
|
||||
"msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
|
||||
"mkword" to FSignature(true, listOf(FParam("lsb", setOf(DataType.UBYTE)), FParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
"mkword" to FSignature(true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
"rnd" to FSignature(true, emptyList(), DataType.UBYTE),
|
||||
"rndw" to FSignature(true, emptyList(), DataType.UWORD),
|
||||
"rndf" to FSignature(true, emptyList(), DataType.FLOAT),
|
||||
@ -87,12 +88,25 @@ val BuiltinFunctions = mapOf(
|
||||
FParam("address", IterableDatatypes + DataType.UWORD),
|
||||
FParam("numwords", setOf(DataType.UWORD)),
|
||||
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
|
||||
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen)
|
||||
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
|
||||
"substr" to FSignature(false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("start", setOf(DataType.UBYTE)),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
"leftstr" to FSignature(false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null),
|
||||
"rightstr" to FSignature(false, listOf(
|
||||
FParam("source", IterableDatatypes + DataType.UWORD),
|
||||
FParam("target", IterableDatatypes + DataType.UWORD),
|
||||
FParam("length", setOf(DataType.UBYTE))), null)
|
||||
)
|
||||
|
||||
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
|
||||
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
|
||||
|
||||
fun builtinMin(array: List<Number>): Number = array.minBy { it.toDouble() }!!
|
||||
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
|
||||
|
||||
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
|
||||
|
||||
@ -172,6 +186,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
|
||||
|
||||
|
||||
class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
||||
class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
|
||||
|
||||
|
||||
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
|
||||
@ -226,54 +241,88 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
// 1 arg, type = anything, result type = ubyte
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("sizeof requires one argument", position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("sizeof argument should be an identifier", position)
|
||||
|
||||
val dt = args[0].inferType(program)
|
||||
if(dt.isKnown) {
|
||||
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
|
||||
?: throw CannotEvaluateException("sizeof", "no target")
|
||||
|
||||
fun structSize(target: StructDecl) =
|
||||
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position)
|
||||
|
||||
return when {
|
||||
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
|
||||
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
||||
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
|
||||
numericLiteral(elementDt.memorySize() * length, position)
|
||||
}
|
||||
dt.istype(DataType.STRUCT) -> {
|
||||
when (target) {
|
||||
is VarDecl -> structSize(target.struct!!)
|
||||
is StructDecl -> structSize(target)
|
||||
else -> throw CompilerException("weird struct type $target")
|
||||
}
|
||||
}
|
||||
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
||||
else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position)
|
||||
}
|
||||
} else {
|
||||
throw SyntaxError("sizeof invalid argument type", position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
if (args.size != 1)
|
||||
throw SyntaxError("strlen requires one argument", position)
|
||||
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
if(argument.type != DataType.STR)
|
||||
throw SyntaxError("strlen must have string argument", position)
|
||||
|
||||
throw NotConstArgumentException() // this function is not considering the string argument a constant
|
||||
val argument=args[0]
|
||||
if(argument is StringLiteralValue)
|
||||
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
|
||||
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
|
||||
if(vardecl!=null) {
|
||||
if(vardecl.datatype!=DataType.STR)
|
||||
throw SyntaxError("strlen must have string argument", position)
|
||||
if(vardecl.autogeneratedDontRemove) {
|
||||
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
|
||||
}
|
||||
}
|
||||
throw NotConstArgumentException()
|
||||
}
|
||||
|
||||
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("len requires one argument", position)
|
||||
val constArg = args[0].constValue(program)
|
||||
if(constArg!=null)
|
||||
throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||
|
||||
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||
var arraySize = directMemVar?.arraysize?.size()
|
||||
var arraySize = directMemVar?.arraysize?.constIndex()
|
||||
if(arraySize != null)
|
||||
return NumericLiteralValue.optimalInteger(arraySize, position)
|
||||
if(args[0] is ArrayLiteralValue)
|
||||
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
throw SyntaxError("len argument should be an identifier", position)
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
|
||||
?: throw CannotEvaluateException("len", "no target vardecl")
|
||||
|
||||
return when(target.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
arraySize = target.arraysize!!.size()!!
|
||||
if(arraySize>256)
|
||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
arraySize = target.arraysize!!.size()!!
|
||||
if(arraySize>256)
|
||||
throw CompilerException("array length exceeds byte limit ${target.position}")
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F -> {
|
||||
arraySize = target.arraysize?.constIndex()
|
||||
if(arraySize==null)
|
||||
throw CannotEvaluateException("len", "arraysize unknown")
|
||||
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
|
||||
}
|
||||
DataType.STR -> {
|
||||
val refLv = target.value as StringLiteralValue
|
||||
if(refLv.value.length>255)
|
||||
throw CompilerException("string length exceeds byte limit ${refLv.position}")
|
||||
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
|
||||
}
|
||||
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
|
||||
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
|
||||
else -> throw CompilerException("weird datatype")
|
||||
}
|
||||
}
|
||||
@ -281,9 +330,9 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
|
||||
|
||||
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
if (args.size != 2)
|
||||
throw SyntaxError("mkword requires lsb and msb arguments", position)
|
||||
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||
throw SyntaxError("mkword requires msb and lsb arguments", position)
|
||||
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
|
||||
return NumericLiteralValue(DataType.UWORD, result, position)
|
||||
}
|
||||
@ -293,7 +342,7 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
|
||||
throw SyntaxError("sin8 requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toShort(), position)
|
||||
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@ -301,7 +350,7 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
|
||||
throw SyntaxError("sin8u requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort(), position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@ -309,7 +358,7 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
|
||||
throw SyntaxError("cos8 requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toShort(), position)
|
||||
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@ -317,7 +366,7 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
|
||||
throw SyntaxError("cos8u requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
|
||||
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort(), position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
|
||||
@ -356,7 +405,7 @@ private fun builtinSgn(args: List<Expression>, position: Position, program: Prog
|
||||
if (args.size != 1)
|
||||
throw SyntaxError("sgn requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toShort(), position)
|
||||
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toInt().toShort(), position)
|
||||
}
|
||||
|
||||
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
|
||||
@ -368,8 +417,8 @@ private fun numericLiteral(value: Number, position: Position): NumericLiteralVal
|
||||
floatNum
|
||||
|
||||
return when(tweakedValue) {
|
||||
is Int -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
|
||||
is Short -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
|
||||
is Int -> NumericLiteralValue.optimalInteger(value.toInt(), position)
|
||||
is Short -> NumericLiteralValue.optimalInteger(value.toInt(), position)
|
||||
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
|
||||
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
||||
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
|
||||
|
@ -14,8 +14,7 @@ import prog8.compiler.loadAsmIncludeFile
|
||||
|
||||
private val alwaysKeepSubroutines = setOf(
|
||||
Pair("main", "start"),
|
||||
Pair("irq", "irq"),
|
||||
Pair("prog8_lib", "init_system")
|
||||
Pair("irq", "irq")
|
||||
)
|
||||
|
||||
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
|
||||
@ -24,12 +23,12 @@ private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexO
|
||||
|
||||
class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
|
||||
val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
|
||||
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
|
||||
|
||||
// TODO add dataflow graph: what statements use what variables
|
||||
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
|
||||
val usedSymbols = mutableSetOf<Statement>()
|
||||
|
||||
init {
|
||||
@ -55,17 +54,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
it.importedBy.clear()
|
||||
it.imports.clear()
|
||||
|
||||
it.importedBy.addAll(modulesImportedBy.getValue(it))
|
||||
it.imports.addAll(modulesImporting.getValue(it))
|
||||
|
||||
forAllSubroutines(it) { sub ->
|
||||
sub.calledBy.clear()
|
||||
sub.calls.clear()
|
||||
|
||||
sub.calledBy.addAll(subroutinesCalledBy.getValue(sub))
|
||||
sub.calls.addAll(subroutinesCalling.getValue(sub))
|
||||
}
|
||||
|
||||
it.importedBy.addAll(importedBy.getValue(it))
|
||||
it.imports.addAll(imports.getValue(it))
|
||||
}
|
||||
|
||||
val rootmodule = program.modules.first()
|
||||
@ -85,8 +75,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val thisModule = directive.definingModule()
|
||||
if (directive.directive == "%import") {
|
||||
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
|
||||
modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule)
|
||||
modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule)
|
||||
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
|
||||
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
|
||||
} else if (directive.directive == "%asminclude") {
|
||||
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
|
||||
val scope = directive.definingScope()
|
||||
@ -127,7 +117,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
|
||||
// make sure autogenerated vardecls are in the used symbols
|
||||
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
|
||||
addNodeAndParentScopes(decl)
|
||||
}
|
||||
|
||||
@ -141,8 +131,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val otherSub = functionCall.target.targetSubroutine(program.namespace)
|
||||
if (otherSub != null) {
|
||||
functionCall.definingSubroutine()?.let { thisSub ->
|
||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCall)
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
|
||||
}
|
||||
}
|
||||
super.visit(functionCall)
|
||||
@ -152,8 +142,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if (otherSub != null) {
|
||||
functionCallStatement.definingSubroutine()?.let { thisSub ->
|
||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCallStatement)
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
|
||||
}
|
||||
}
|
||||
super.visit(functionCallStatement)
|
||||
@ -163,8 +153,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
|
||||
if (otherSub != null) {
|
||||
jump.definingSubroutine()?.let { thisSub ->
|
||||
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
|
||||
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(jump)
|
||||
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
|
||||
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
|
||||
}
|
||||
}
|
||||
super.visit(jump)
|
||||
@ -190,14 +180,14 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
|
||||
val node = program.namespace.lookup(jumptarget.split('.'), context)
|
||||
if (node is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
} else if (jumptarget.contains('.')) {
|
||||
// maybe only the first part already refers to a subroutine
|
||||
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
|
||||
if (node2 is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node2)
|
||||
subroutinesCalledBy[node2] = subroutinesCalledBy.getValue(node2).plus(context)
|
||||
calls[scope] = calls.getValue(scope).plus(node2)
|
||||
calledBy[node2] = calledBy.getValue(node2).plus(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,8 +199,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
|
||||
if (target.contains('.')) {
|
||||
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
|
||||
if (node is Subroutine) {
|
||||
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
|
||||
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
|
||||
calls[scope] = calls.getValue(scope).plus(node)
|
||||
calledBy[node] = calledBy.getValue(node).plus(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,11 +132,11 @@ class ConstExprEvaluator {
|
||||
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
|
||||
if(left.type== DataType.UBYTE) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
|
||||
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toShort(), left.position)
|
||||
}
|
||||
} else if(left.type== DataType.UWORD) {
|
||||
if(right.type in IntegerDatatypes) {
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
|
||||
return NumericLiteralValue(DataType.UWORD, left.number.toInt() and right.number.toInt(), left.position)
|
||||
}
|
||||
}
|
||||
throw ExpressionError("cannot calculate $left & $right", left.position)
|
||||
@ -163,7 +163,7 @@ class ConstExprEvaluator {
|
||||
val error = "cannot add $left and $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@ -180,7 +180,7 @@ class ConstExprEvaluator {
|
||||
val error = "cannot subtract $left and $right"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@ -197,7 +197,7 @@ class ConstExprEvaluator {
|
||||
val error = "cannot multiply ${left.type} and ${right.type}"
|
||||
return when (left.type) {
|
||||
in IntegerDatatypes -> when (right.type) {
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
|
||||
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
|
||||
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
@ -220,7 +220,7 @@ class ConstExprEvaluator {
|
||||
in IntegerDatatypes -> {
|
||||
if(right.number.toInt()==0) divideByZeroError(right.position)
|
||||
val result: Int = left.number.toInt() / right.number.toInt()
|
||||
NumericLiteralValue.optimalNumeric(result, left.position)
|
||||
NumericLiteralValue.optimalInteger(result, left.position)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
|
||||
|
@ -1,24 +1,46 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
|
||||
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
// 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 visit(decl: VarDecl): Statement {
|
||||
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 tree for this?
|
||||
// TODO: use call graph for this?
|
||||
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
|
||||
errors.err("recursive var declaration", decl.position)
|
||||
return decl
|
||||
return noModifications
|
||||
}
|
||||
|
||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||
@ -27,15 +49,20 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||
val arrayval = decl.value as? ArrayLiteralValue
|
||||
if(arrayval!=null) {
|
||||
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position)
|
||||
optimizationsDone++
|
||||
return listOf(IAstModification.SetExpression(
|
||||
{ decl.arraysize = ArrayIndex(it, decl.position) },
|
||||
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
|
||||
decl
|
||||
))
|
||||
}
|
||||
}
|
||||
else if(decl.arraysize?.size()==null) {
|
||||
val size = decl.arraysize!!.index.accept(this)
|
||||
if(size is NumericLiteralValue) {
|
||||
decl.arraysize = ArrayIndex(size, decl.position)
|
||||
optimizationsDone++
|
||||
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
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,9 +73,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if (litval!=null && litval.type in IntegerDatatypes) {
|
||||
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
|
||||
decl.value = newValue
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
@ -56,29 +81,27 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array
|
||||
val declArraySize = decl.arraysize?.size()
|
||||
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)
|
||||
if(eltType in ByteDatatypes) {
|
||||
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
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 {
|
||||
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
|
||||
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
|
||||
position = decl.value!!.position)
|
||||
}
|
||||
decl.value!!.linkParents(decl)
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
|
||||
}
|
||||
}
|
||||
if(numericLv!=null && numericLv.type== DataType.FLOAT)
|
||||
if(numericLv!=null && numericLv.type==DataType.FLOAT)
|
||||
errors.err("arraysize requires only integers here", numericLv.position)
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
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()
|
||||
@ -102,33 +125,38 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
else -> {}
|
||||
}
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) as Expression}.toTypedArray()
|
||||
val 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)
|
||||
decl.value = refValue
|
||||
refValue.parent=decl
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
val size = decl.arraysize?.constIndex() ?: return noModifications
|
||||
val litval = decl.value as? NumericLiteralValue
|
||||
if(litval==null) {
|
||||
// there's no initialization value, but the size is known, so we're ok.
|
||||
return super.visit(decl)
|
||||
} else {
|
||||
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.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
|
||||
errors.err("float value overflow", litval.position)
|
||||
else {
|
||||
// create the array itself, filled with the fillvalue.
|
||||
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
|
||||
val 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)
|
||||
decl.value = refValue
|
||||
refValue.parent=decl
|
||||
optimizationsDone++
|
||||
return super.visit(decl)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,135 +171,71 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
if(declValue!=null && decl.type==VarDeclType.VAR
|
||||
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
|
||||
// cast the numeric literal to the appropriate datatype of the variable
|
||||
decl.value = declValue.cast(decl.datatype)
|
||||
val cast = declValue.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
|
||||
}
|
||||
|
||||
return super.visit(decl)
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
|
||||
*/
|
||||
override fun visit(identifier: IdentifierReference): Expression {
|
||||
// don't replace when it's an assignment target or loop variable
|
||||
if(identifier.parent is AssignTarget)
|
||||
return identifier
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if(forloop==null)
|
||||
forloop = identifier.parent.parent as? ForLoop
|
||||
if(forloop!=null && identifier===forloop.loopVar)
|
||||
return identifier
|
||||
|
||||
val cval = identifier.constValue(program) ?: return identifier
|
||||
return when (cval.type) {
|
||||
in NumericDatatypes -> {
|
||||
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
|
||||
copy.parent = identifier.parent
|
||||
copy
|
||||
}
|
||||
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
|
||||
else -> identifier
|
||||
}
|
||||
}
|
||||
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
super.visit(functionCall)
|
||||
typeCastConstArguments(functionCall)
|
||||
return functionCall.constValue(program) ?: functionCall
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
super.visit(functionCallStatement)
|
||||
typeCastConstArguments(functionCallStatement)
|
||||
return functionCallStatement
|
||||
}
|
||||
|
||||
private fun typeCastConstArguments(functionCall: IFunctionCall) {
|
||||
if(functionCall.target.nameInSource.size==1) {
|
||||
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
|
||||
if(builtinFunction!=null) {
|
||||
// match the arguments of a builtin function signature.
|
||||
for(arg in functionCall.args.withIndex().zip(builtinFunction.parameters)) {
|
||||
val possibleDts = arg.second.possibleDatatypes
|
||||
val argConst = arg.first.value.constValue(program)
|
||||
if(argConst!=null && argConst.type !in possibleDts) {
|
||||
val convertedValue = argConst.cast(possibleDts.first())
|
||||
functionCall.args[arg.first.index] = convertedValue
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
// match the arguments of a subroutine.
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
|
||||
for(arg in functionCall.args.withIndex().zip(subroutine.parameters)) {
|
||||
val expectedDt = arg.second.type
|
||||
val argConst = arg.first.value.constValue(program)
|
||||
if(argConst!=null && argConst.type!=expectedDt) {
|
||||
val convertedValue = argConst.cast(expectedDt)
|
||||
functionCall.args[arg.first.index] = convertedValue
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(memread: DirectMemoryRead): Expression {
|
||||
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
|
||||
// @( &thing ) --> thing
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf!=null)
|
||||
return super.visit(addrOf.identifier)
|
||||
return super.visit(memread)
|
||||
return if(addrOf!=null)
|
||||
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to accept a unary prefix expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||
*/
|
||||
override fun visit(expr: PrefixExpression): Expression {
|
||||
val prefixExpr=super.visit(expr)
|
||||
if(prefixExpr !is PrefixExpression)
|
||||
return prefixExpr
|
||||
|
||||
val subexpr = prefixExpr.expression
|
||||
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
|
||||
// Try to turn a unary prefix expression into a single constant value.
|
||||
// Compile-time constant sub expressions will be evaluated on the spot.
|
||||
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
|
||||
val subexpr = expr.expression
|
||||
if (subexpr is NumericLiteralValue) {
|
||||
// accept prefixed literal values (such as -3, not true)
|
||||
return when (prefixExpr.operator) {
|
||||
"+" -> subexpr
|
||||
return when (expr.operator) {
|
||||
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
|
||||
"-" -> when (subexpr.type) {
|
||||
in IntegerDatatypes -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.optimalInteger(-subexpr.number.toInt(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
|
||||
}
|
||||
"~" -> when (subexpr.type) {
|
||||
in IntegerDatatypes -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.optimalInteger(subexpr.number.toInt().inv(), subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
|
||||
}
|
||||
"not" -> {
|
||||
optimizationsDone++
|
||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
|
||||
listOf(IAstModification.ReplaceNode(expr,
|
||||
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
|
||||
parent))
|
||||
}
|
||||
else -> throw ExpressionError(prefixExpr.operator, subexpr.position)
|
||||
else -> throw ExpressionError(expr.operator, subexpr.position)
|
||||
}
|
||||
}
|
||||
return prefixExpr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to accept a binary expression.
|
||||
* Try to constfold a binary expression.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
|
||||
*
|
||||
@ -287,13 +251,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
* (X / c1) * c2 -> X / (c2/c1)
|
||||
* (X + c1) - c2 -> X + (c1-c2)
|
||||
*/
|
||||
override fun visit(expr: BinaryExpression): Expression {
|
||||
super.visit(expr)
|
||||
|
||||
if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
|
||||
|| expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
|
||||
throw FatalAstException("binexpr with reference litval instead of numeric")
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
val leftconst = expr.left.constValue(program)
|
||||
val rightconst = expr.right.constValue(program)
|
||||
|
||||
@ -307,21 +265,153 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
val subrightconst = subExpr.right.constValue(program)
|
||||
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
|
||||
// try reordering.
|
||||
return groupTwoConstsTogether(expr, subExpr,
|
||||
val change = groupTwoConstsTogether(expr, subExpr,
|
||||
leftconst != null, rightconst != null,
|
||||
subleftconst != null, subrightconst != null)
|
||||
return change?.let { listOf(it) } ?: noModifications
|
||||
}
|
||||
}
|
||||
|
||||
// const fold when both operands are a const
|
||||
return when {
|
||||
leftconst != null && rightconst != null -> {
|
||||
optimizationsDone++
|
||||
val evaluator = ConstExprEvaluator()
|
||||
evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
}
|
||||
if(leftconst != null && rightconst != null) {
|
||||
val evaluator = ConstExprEvaluator()
|
||||
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
return listOf(IAstModification.ReplaceNode(expr, result, parent))
|
||||
}
|
||||
|
||||
else -> expr
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
|
||||
// because constant folding can result in arrays that are now suddenly capable
|
||||
// of telling the type of all their elements (for instance, when they contained -2 which
|
||||
// was a prefix expression earlier), we recalculate the array's datatype.
|
||||
if(array.type.isKnown)
|
||||
return noModifications
|
||||
|
||||
// if the array literalvalue is inside an array vardecl, take the type from that
|
||||
// otherwise infer it from the elements of the array
|
||||
val vardeclType = (array.parent as? VarDecl)?.datatype
|
||||
if(vardeclType!=null) {
|
||||
val newArray = array.cast(vardeclType)
|
||||
if (newArray != null && newArray != array)
|
||||
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||
} else {
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if (arrayDt.isKnown) {
|
||||
val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if (newArray != null && newArray != array)
|
||||
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// the args of a fuction are constfolded via recursion already.
|
||||
val constvalue = functionCall.constValue(program)
|
||||
return if(constvalue!=null)
|
||||
listOf(IAstModification.ReplaceNode(functionCall, constvalue, parent))
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr? {
|
||||
val fromCast = rangeFrom.cast(targetDt)
|
||||
val toCast = rangeTo.cast(targetDt)
|
||||
if(!fromCast.isValid || !toCast.isValid)
|
||||
return null
|
||||
|
||||
val newStep =
|
||||
if(stepLiteral!=null) {
|
||||
val stepCast = stepLiteral.cast(targetDt)
|
||||
if(stepCast.isValid)
|
||||
stepCast.valueOrZero()
|
||||
else
|
||||
range.step
|
||||
} else {
|
||||
range.step
|
||||
}
|
||||
|
||||
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
|
||||
}
|
||||
|
||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||
val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications
|
||||
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
||||
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||
if(rangeFrom==null || rangeTo==null) return noModifications
|
||||
|
||||
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!!
|
||||
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
||||
when(loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(rangeFrom.type!= DataType.UBYTE) {
|
||||
// attempt to translate the iterable into ubyte values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(rangeFrom.type!= DataType.BYTE) {
|
||||
// attempt to translate the iterable into byte values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(rangeFrom.type!= DataType.UWORD) {
|
||||
// attempt to translate the iterable into uword values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(rangeFrom.type!= DataType.WORD) {
|
||||
// attempt to translate the iterable into word values
|
||||
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
if(newIter!=null)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val numval = decl.value as? NumericLiteralValue
|
||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||
val valueDt = numval.inferType(program)
|
||||
if(!valueDt.istype(decl.datatype)) {
|
||||
val cast = numval.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private class ShuffleOperands(val expr: BinaryExpression,
|
||||
val exprOperator: String?,
|
||||
val subExpr: BinaryExpression,
|
||||
val newExprLeft: Expression?,
|
||||
val newExprRight: Expression?,
|
||||
val newSubexprLeft: Expression?,
|
||||
val newSubexprRight: Expression?
|
||||
): IAstModification {
|
||||
override fun perform() {
|
||||
if(exprOperator!=null) expr.operator = exprOperator
|
||||
if(newExprLeft!=null) expr.left = newExprLeft
|
||||
if(newExprRight!=null) expr.right = newExprRight
|
||||
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
|
||||
if(newSubexprRight!=null) subExpr.right = newSubexprRight
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,64 +420,61 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
leftIsConst: Boolean,
|
||||
rightIsConst: Boolean,
|
||||
subleftIsConst: Boolean,
|
||||
subrightIsConst: Boolean): Expression
|
||||
subrightIsConst: Boolean): IAstModification?
|
||||
{
|
||||
// todo: this implements only a small set of possible reorderings at this time
|
||||
if(expr.operator==subExpr.operator) {
|
||||
// both operators are the isSameAs.
|
||||
// If + or *, we can simply swap the const of expr and Var in subexpr.
|
||||
if(expr.operator=="+" || expr.operator=="*") {
|
||||
if(leftIsConst) {
|
||||
// both operators are the same.
|
||||
|
||||
// If associative, we can simply shuffle the const operands around to optimize.
|
||||
if(expr.operator in associativeOperators) {
|
||||
return if(leftIsConst) {
|
||||
if(subleftIsConst)
|
||||
expr.left = subExpr.right.also { subExpr.right = expr.left }
|
||||
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
|
||||
else
|
||||
expr.left = subExpr.left.also { subExpr.left = expr.left }
|
||||
ShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
|
||||
} else {
|
||||
if(subleftIsConst)
|
||||
expr.right = subExpr.right.also {subExpr.right = expr.right }
|
||||
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
else
|
||||
expr.right = subExpr.left.also { subExpr.left = expr.right }
|
||||
ShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
|
||||
}
|
||||
optimizationsDone++
|
||||
return expr
|
||||
}
|
||||
|
||||
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
|
||||
if(expr.operator=="-" || expr.operator=="/") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
val tmp = subExpr.right
|
||||
subExpr.right = subExpr.left
|
||||
subExpr.left = expr.left
|
||||
expr.left = tmp
|
||||
expr.operator = if(expr.operator=="-") "+" else "*"
|
||||
expr
|
||||
} else
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.operator, subExpr.left, expr.position)
|
||||
return if (subleftIsConst) {
|
||||
ShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
|
||||
} else {
|
||||
IAstModification.ReplaceNode(expr,
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.operator, subExpr.left, expr.position),
|
||||
expr.parent)
|
||||
}
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
expr.right = subExpr.right.also { subExpr.right = expr.right }
|
||||
expr
|
||||
} else
|
||||
BinaryExpression(
|
||||
subExpr.left, expr.operator,
|
||||
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.position)
|
||||
return ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
|
||||
} else {
|
||||
IAstModification.ReplaceNode(expr,
|
||||
BinaryExpression(
|
||||
subExpr.left, expr.operator,
|
||||
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
|
||||
expr.position),
|
||||
expr.parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
return expr
|
||||
return null
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
if(expr.operator=="/" && subExpr.operator=="*") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// C1/(C2*V) -> (C1/C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
|
||||
@ -400,8 +487,9 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"/",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// (C1*V)/C2 -> (C1/C2)*V
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
|
||||
@ -414,12 +502,12 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="*" && subExpr.operator=="/") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// C1*(C2/V) -> (C1*C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
|
||||
@ -432,8 +520,9 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// (C1/V)*C2 -> (C1*C2)/V
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
|
||||
@ -446,12 +535,12 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"*",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="+" && subExpr.operator=="-") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst){
|
||||
return if(subleftIsConst){
|
||||
val change = if(subleftIsConst){
|
||||
// c1+(c2-v) -> (c1+c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
|
||||
@ -464,8 +553,9 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// (c1-v)+c2 -> (c1+c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
|
||||
@ -478,12 +568,12 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
else if(expr.operator=="-" && subExpr.operator=="+") {
|
||||
optimizationsDone++
|
||||
if(leftIsConst) {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// c1-(c2+v) -> (c1-c2)-v
|
||||
BinaryExpression(
|
||||
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
|
||||
@ -496,8 +586,9 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"-",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
} else {
|
||||
return if(subleftIsConst) {
|
||||
val change = if(subleftIsConst) {
|
||||
// (c1+v)-c2 -> v+(c1-c2)
|
||||
BinaryExpression(
|
||||
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
|
||||
@ -510,113 +601,12 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
|
||||
"+",
|
||||
subExpr.left, expr.position)
|
||||
}
|
||||
return IAstModification.ReplaceNode(expr, change, expr.parent)
|
||||
}
|
||||
}
|
||||
|
||||
return expr
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop): Statement {
|
||||
|
||||
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
|
||||
val newFrom: NumericLiteralValue
|
||||
val newTo: NumericLiteralValue
|
||||
try {
|
||||
newFrom = rangeFrom.cast(targetDt)
|
||||
newTo = rangeTo.cast(targetDt)
|
||||
} catch (x: ExpressionError) {
|
||||
return range
|
||||
}
|
||||
val newStep: Expression = try {
|
||||
stepLiteral?.cast(targetDt)?: range.step
|
||||
} catch(ee: ExpressionError) {
|
||||
range.step
|
||||
}
|
||||
return RangeExpr(newFrom, newTo, newStep, range.position)
|
||||
}
|
||||
|
||||
val forLoop2 = super.visit(forLoop) as ForLoop
|
||||
|
||||
// check if we need to adjust an array literal to the loop variable's datatype
|
||||
val array = forLoop2.iterable as? ArrayLiteralValue
|
||||
if(array!=null) {
|
||||
val loopvarDt: DataType = when {
|
||||
forLoop.loopVar!=null -> forLoop.loopVar!!.inferType(program).typeOrElse(DataType.UBYTE)
|
||||
forLoop.loopRegister!=null -> DataType.UBYTE
|
||||
else -> throw FatalAstException("weird for loop")
|
||||
}
|
||||
|
||||
val arrayType = when(loopvarDt) {
|
||||
DataType.UBYTE -> DataType.ARRAY_UB
|
||||
DataType.BYTE -> DataType.ARRAY_B
|
||||
DataType.UWORD -> DataType.ARRAY_UW
|
||||
DataType.WORD -> DataType.ARRAY_W
|
||||
DataType.FLOAT -> DataType.ARRAY_F
|
||||
else -> throw FatalAstException("invalid array elt type")
|
||||
}
|
||||
val array2 = array.cast(arrayType)
|
||||
if(array2!=null && array2!==array) {
|
||||
forLoop2.iterable = array2
|
||||
array2.linkParents(forLoop2)
|
||||
}
|
||||
}
|
||||
|
||||
// adjust the datatype of a range expression in for loops to the loop variable.
|
||||
val iterableRange = forLoop2.iterable as? RangeExpr ?: return forLoop2
|
||||
val rangeFrom = iterableRange.from as? NumericLiteralValue
|
||||
val rangeTo = iterableRange.to as? NumericLiteralValue
|
||||
if(rangeFrom==null || rangeTo==null) return forLoop2
|
||||
|
||||
val loopvar = forLoop2.loopVar?.targetVarDecl(program.namespace)
|
||||
if(loopvar!=null) {
|
||||
val stepLiteral = iterableRange.step as? NumericLiteralValue
|
||||
when(loopvar.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
if(rangeFrom.type!= DataType.UBYTE) {
|
||||
// attempt to translate the iterable into ubyte values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(rangeFrom.type!= DataType.BYTE) {
|
||||
// attempt to translate the iterable into byte values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(rangeFrom.type!= DataType.UWORD) {
|
||||
// attempt to translate the iterable into uword values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(rangeFrom.type!= DataType.WORD) {
|
||||
// attempt to translate the iterable into word values
|
||||
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
|
||||
}
|
||||
}
|
||||
return forLoop2
|
||||
}
|
||||
|
||||
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
|
||||
// because constant folding can result in arrays that are now suddenly capable
|
||||
// of telling the type of all their elements (for instance, when they contained -2 which
|
||||
// was a prefix expression earlier), we recalculate the array's datatype.
|
||||
val array = super.visit(arrayLiteral)
|
||||
if(array is ArrayLiteralValue) {
|
||||
if(array.type.isKnown)
|
||||
return array
|
||||
val arrayDt = array.guessDatatype(program)
|
||||
if(arrayDt.isKnown) {
|
||||
val newArray = arrayLiteral.cast(arrayDt.typeOrElse(DataType.STRUCT))
|
||||
if(newArray!=null)
|
||||
return newArray
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.Assignment
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
@ -14,12 +13,6 @@ import kotlin.math.pow
|
||||
/*
|
||||
todo add more expression optimizations
|
||||
|
||||
x + x -> x << 1 (for words... for bytes too?)
|
||||
x + x + x + x -> x << 2 (for words... for bytes too?)
|
||||
x + x + x -> ???? x*3 ??? words/bytes?
|
||||
x - x -> 0
|
||||
|
||||
|
||||
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||
|
||||
*/
|
||||
@ -28,12 +21,7 @@ import kotlin.math.pow
|
||||
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
|
||||
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if (assignment.aug_op != null)
|
||||
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
return emptyList()
|
||||
}
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
|
||||
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
val mods = mutableListOf<IAstModification>()
|
||||
@ -42,19 +30,22 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
val literal = typecast.expression as? NumericLiteralValue
|
||||
if (literal != null) {
|
||||
val newLiteral = literal.cast(typecast.type)
|
||||
if (newLiteral !== literal)
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral, typecast)
|
||||
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal)
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral.valueOrZero(), typecast)
|
||||
}
|
||||
|
||||
// remove redundant nested typecasts:
|
||||
// if the typecast casts a value to the same type, remove the cast.
|
||||
// if the typecast contains another typecast, remove the inner typecast.
|
||||
// remove redundant nested typecasts
|
||||
val subTypecast = typecast.expression as? TypecastExpression
|
||||
if (subTypecast != null) {
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
||||
// remove the sub-typecast if its datatype is larger than the outer typecast
|
||||
if(subTypecast.type largerThan typecast.type) {
|
||||
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
|
||||
}
|
||||
} else {
|
||||
if (typecast.expression.inferType(program).istype(typecast.type))
|
||||
if (typecast.expression.inferType(program).istype(typecast.type)) {
|
||||
// remove duplicate cast
|
||||
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
|
||||
}
|
||||
}
|
||||
|
||||
return mods
|
||||
@ -88,10 +79,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (newExpr != null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
}
|
||||
else -> return emptyList()
|
||||
else -> return noModifications
|
||||
}
|
||||
}
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
@ -103,7 +94,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
throw FatalAstException("can't determine datatype of both expression operands $expr")
|
||||
|
||||
|
||||
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
|
||||
if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
|
||||
return listOf(IAstModification.SwapOperands(expr))
|
||||
@ -180,6 +170,64 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == ">=" && rightVal?.number == 0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned >= 0 --> true
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed >=0 --> signed ^ $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw >=0 --> msb(signedw) ^ $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator == "<" && rightVal?.number == 0) {
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
// unsigned < 0 --> false
|
||||
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
|
||||
}
|
||||
when(leftDt) {
|
||||
DataType.BYTE -> {
|
||||
// signed < 0 --> signed & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
DataType.WORD -> {
|
||||
// signedw < 0 --> msb(signedw) & $80
|
||||
return listOf(IAstModification.ReplaceNode(
|
||||
expr,
|
||||
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
|
||||
mutableListOf(expr.left),
|
||||
expr.position
|
||||
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
|
||||
parent
|
||||
))
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
// simplify when a term is constant and directly determines the outcome
|
||||
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
|
||||
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
|
||||
@ -246,7 +294,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if(newExpr != null)
|
||||
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
|
||||
|
||||
return emptyList()
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
|
||||
@ -538,10 +586,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
} else if (amount >= 8) {
|
||||
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
|
||||
if (amount == 8) {
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position)
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position)
|
||||
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@ -558,8 +606,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
|
||||
if (amount == 0) {
|
||||
return expr.left
|
||||
}
|
||||
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
|
||||
when (targetDt) {
|
||||
when (expr.left.inferType(program).typeOrElse(DataType.STRUCT)) {
|
||||
DataType.UBYTE -> {
|
||||
if (amount >= 8) {
|
||||
return NumericLiteralValue.optimalInteger(0, expr.position)
|
||||
|
@ -5,12 +5,21 @@ import prog8.ast.base.ErrorReporter
|
||||
|
||||
|
||||
internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
val optimizer = ConstantFoldingOptimizer(this, errors)
|
||||
optimizer.visit(this)
|
||||
val replacer = ConstantIdentifierReplacer(this, errors)
|
||||
replacer.visit(this)
|
||||
if(errors.isEmpty()) {
|
||||
replacer.applyModifications()
|
||||
|
||||
while(errors.isEmpty() && optimizer.optimizationsDone>0) {
|
||||
optimizer.optimizationsDone = 0
|
||||
val optimizer = ConstantFoldingOptimizer(this)
|
||||
optimizer.visit(this)
|
||||
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
|
||||
optimizer.visit(this)
|
||||
}
|
||||
|
||||
if(errors.isEmpty()) {
|
||||
replacer.visit(this)
|
||||
replacer.applyModifications()
|
||||
}
|
||||
}
|
||||
|
||||
if(errors.isEmpty())
|
||||
@ -21,9 +30,11 @@ internal fun Program.constantFold(errors: ErrorReporter) {
|
||||
internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
|
||||
val optimizer = StatementOptimizer(this, errors)
|
||||
optimizer.visit(this)
|
||||
val optimizationCount = optimizer.applyModifications()
|
||||
|
||||
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
||||
|
||||
return optimizer.optimizationsDone
|
||||
return optimizationCount
|
||||
}
|
||||
|
||||
internal fun Program.simplifyExpressions() : Int {
|
||||
|
@ -1,46 +0,0 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.NopStatement
|
||||
import prog8.ast.statements.Statement
|
||||
|
||||
internal class FlattenAnonymousScopesAndNopRemover: IAstVisitor {
|
||||
private var scopesToFlatten = mutableListOf<INameScope>()
|
||||
private val nopStatements = mutableListOf<NopStatement>()
|
||||
|
||||
override fun visit(program: Program) {
|
||||
super.visit(program)
|
||||
for(scope in scopesToFlatten.reversed()) {
|
||||
val namescope = scope.parent as INameScope
|
||||
val idx = namescope.statements.indexOf(scope as Statement)
|
||||
if(idx>=0) {
|
||||
val nop = NopStatement.insteadOf(namescope.statements[idx])
|
||||
nop.parent = namescope as Node
|
||||
namescope.statements[idx] = nop
|
||||
namescope.statements.addAll(idx, scope.statements)
|
||||
scope.statements.forEach { it.parent = namescope }
|
||||
visit(nop)
|
||||
}
|
||||
}
|
||||
|
||||
this.nopStatements.forEach {
|
||||
it.definingScope().remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(scope: AnonymousScope) {
|
||||
if(scope.parent is INameScope) {
|
||||
scopesToFlatten.add(scope) // get rid of the anonymous scope
|
||||
}
|
||||
|
||||
return super.visit(scope)
|
||||
}
|
||||
|
||||
override fun visit(nopStatement: NopStatement) {
|
||||
nopStatements.add(nopStatement)
|
||||
}
|
||||
}
|
@ -1,106 +1,51 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.*
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.processing.IAstModifyingVisitor
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.processing.IAstVisitor
|
||||
import prog8.ast.statements.*
|
||||
import prog8.compiler.target.CompilationTarget
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
/*
|
||||
TODO: remove unreachable code after return and exit()
|
||||
TODO: proper inlining of tiny subroutines (at first, restrict to subs without parameters and variables in them, and build it up from there: correctly renaming/relocating all variables in them and refs to those as well)
|
||||
*/
|
||||
|
||||
|
||||
internal class StatementOptimizer(private val program: Program,
|
||||
private val errors: ErrorReporter) : IAstModifyingVisitor {
|
||||
var optimizationsDone: Int = 0
|
||||
private set
|
||||
private val errors: ErrorReporter) : AstWalker() {
|
||||
|
||||
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
||||
private val noModifications = emptyList<IAstModification>()
|
||||
private val callgraph = CallGraph(program)
|
||||
private val vardeclsToRemove = mutableListOf<VarDecl>()
|
||||
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
|
||||
|
||||
override fun visit(program: Program) {
|
||||
removeUnusedCode(callgraph)
|
||||
super.visit(program)
|
||||
|
||||
for(decl in vardeclsToRemove) {
|
||||
decl.definingScope().remove(decl)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeUnusedCode(callgraph: CallGraph) {
|
||||
// remove all subroutines that aren't called, or are empty
|
||||
val removeSubroutines = mutableSetOf<Subroutine>()
|
||||
val entrypoint = program.entrypoint()
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
|
||||
removeSubroutines.add(sub)
|
||||
}
|
||||
}
|
||||
|
||||
if (removeSubroutines.isNotEmpty()) {
|
||||
removeSubroutines.forEach {
|
||||
it.definingScope().remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
val removeBlocks = mutableSetOf<Block>()
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||
removeBlocks.add(block)
|
||||
}
|
||||
|
||||
if (removeBlocks.isNotEmpty()) {
|
||||
removeBlocks.forEach { it.definingScope().remove(it) }
|
||||
}
|
||||
|
||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||
val removeModules = mutableSetOf<Module>()
|
||||
program.modules.forEach {
|
||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||
removeModules.add(it)
|
||||
}
|
||||
|
||||
if (removeModules.isNotEmpty()) {
|
||||
program.modules.removeAll(removeModules)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(block: Block): Statement {
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
optimizationsDone++
|
||||
errors.warn("removing empty block '${block.name}'", block.position)
|
||||
return NopStatement.insteadOf(block)
|
||||
return listOf(IAstModification.Remove(block, parent))
|
||||
}
|
||||
|
||||
if (block !in callgraph.usedSymbols) {
|
||||
optimizationsDone++
|
||||
errors.warn("removing unused block '${block.name}'", block.position)
|
||||
return NopStatement.insteadOf(block) // remove unused block
|
||||
return listOf(IAstModification.Remove(block, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return super.visit(block)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine): Statement {
|
||||
super.visit(subroutine)
|
||||
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
||||
if(subroutine.asmAddress==null && !forceOutput) {
|
||||
if(subroutine.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(subroutine)
|
||||
val removals = callgraph.calledBy.getValue(subroutine).map {
|
||||
IAstModification.Remove(it, it.parent)
|
||||
}.toMutableList()
|
||||
removals += IAstModification.Remove(subroutine, parent)
|
||||
return removals
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,23 +56,398 @@ internal class StatementOptimizer(private val program: Program,
|
||||
|
||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(subroutine)
|
||||
return listOf(IAstModification.Remove(subroutine, parent))
|
||||
}
|
||||
|
||||
return subroutine
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl): Statement {
|
||||
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
|
||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||
return linesToRemove.reversed().map { IAstModification.Remove(scope.statements[it], scope) }
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
val forceOutput = "force_output" in decl.definingBlock().options()
|
||||
if(decl !in callgraph.usedSymbols && !forceOutput) {
|
||||
if(decl.type == VarDeclType.VAR)
|
||||
errors.warn("removing unused variable ${decl.type} '${decl.name}'", decl.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(decl)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
|
||||
return listOf(IAstModification.Remove(decl, parent))
|
||||
}
|
||||
|
||||
return super.visit(decl)
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in pureBuiltinFunctions) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent))
|
||||
}
|
||||
}
|
||||
|
||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||
// this is a C-64 specific optimization
|
||||
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print")) {
|
||||
val arg = functionCallStatement.args.single()
|
||||
val stringVar: IdentifierReference?
|
||||
stringVar = if(arg is AddressOf) {
|
||||
arg.identifier
|
||||
} else {
|
||||
arg as? IdentifierReference
|
||||
}
|
||||
if(stringVar!=null) {
|
||||
val vardecl = stringVar.targetVarDecl(program.namespace)!!
|
||||
val string = vardecl.value as? StringLiteralValue
|
||||
if(string!=null) {
|
||||
val pos = functionCallStatement.position
|
||||
if (string.value.length == 1) {
|
||||
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
val chrout = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
|
||||
} else if (string.value.length == 2) {
|
||||
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val chrout1 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val chrout2 = FunctionCallStatement(
|
||||
IdentifierReference(listOf("c64", "CHROUT"), pos),
|
||||
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
|
||||
functionCallStatement.void, pos
|
||||
)
|
||||
val anonscope = AnonymousScope(mutableListOf(), pos)
|
||||
anonscope.statements.add(chrout1)
|
||||
anonscope.statements.add(chrout2)
|
||||
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
|
||||
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return)
|
||||
return listOf(IAstModification.Remove(functionCallStatement, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
|
||||
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Return && first.value!=null) {
|
||||
val constval = first.value?.constValue(program)
|
||||
if(constval!=null)
|
||||
return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty if statements
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
|
||||
return listOf(IAstModification.Remove(ifStatement, parent))
|
||||
|
||||
// empty true part? switch with the else part
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
||||
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
||||
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(ifStatement.condition, invertedCondition, ifStatement),
|
||||
IAstModification.ReplaceNode(ifStatement.truepart, truepart, ifStatement),
|
||||
IAstModification.ReplaceNode(ifStatement.elsepart, emptyscope, ifStatement)
|
||||
)
|
||||
}
|
||||
|
||||
val constvalue = ifStatement.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
errors.warn("condition is always true", ifStatement.position)
|
||||
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.truepart, parent))
|
||||
} else {
|
||||
// always false -> keep only else-part
|
||||
errors.warn("condition is always false", ifStatement.position)
|
||||
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.elsepart, parent))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
|
||||
if(forLoop.body.containsNoCodeNorVars()) {
|
||||
errors.warn("removing empty for loop", forLoop.position)
|
||||
return listOf(IAstModification.Remove(forLoop, parent))
|
||||
} else if(forLoop.body.statements.size==1) {
|
||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
|
||||
// remove empty for loop (only loopvar decl in it)
|
||||
return listOf(IAstModification.Remove(forLoop, parent))
|
||||
}
|
||||
}
|
||||
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
if(range.size()==1) {
|
||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||
// loopvar/reg = range value , follow by block
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
}
|
||||
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program.namespace)
|
||||
if(iterable!=null) {
|
||||
if(iterable.datatype==DataType.STR) {
|
||||
val sv = iterable.value as StringLiteralValue
|
||||
val size = sv.value.length
|
||||
if(size==1) {
|
||||
// loop over string of length 1 -> just assign the single character
|
||||
val character = CompilationTarget.encodeString(sv.value, sv.altEncoding)[0]
|
||||
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
}
|
||||
else if(iterable.datatype in ArrayDatatypes) {
|
||||
val size = iterable.arraysize!!.constIndex()
|
||||
if(size==1) {
|
||||
// loop over array of length 1 -> just assign the single value
|
||||
val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number
|
||||
if(av!=null) {
|
||||
val scope = AnonymousScope(mutableListOf(), forLoop.position)
|
||||
scope.statements.add(Assignment(
|
||||
AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
|
||||
forLoop.position))
|
||||
scope.statements.addAll(forLoop.body.statements)
|
||||
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = untilLoop.untilCondition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
if(constvalue.asBooleanValue) {
|
||||
// always true -> keep only the statement block (if there are no break statements)
|
||||
errors.warn("condition is always true", untilLoop.untilCondition.position)
|
||||
if(!hasBreak(untilLoop.body))
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
|
||||
} else {
|
||||
// always false
|
||||
val forever = RepeatLoop(null, untilLoop.body, untilLoop.position)
|
||||
return listOf(IAstModification.ReplaceNode(untilLoop, forever, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
|
||||
val constvalue = whileLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue) {
|
||||
// always true
|
||||
val forever = RepeatLoop(null, whileLoop.body, whileLoop.position)
|
||||
listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
|
||||
} else {
|
||||
// always false -> remove the while statement altogether
|
||||
errors.warn("condition is always false", whileLoop.condition.position)
|
||||
listOf(IAstModification.Remove(whileLoop, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
|
||||
val iter = repeatLoop.iterations
|
||||
if(iter!=null) {
|
||||
if(repeatLoop.body.containsNoCodeNorVars()) {
|
||||
errors.warn("empty loop removed", repeatLoop.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, parent))
|
||||
}
|
||||
val iterations = iter.constValue(program)?.number?.toInt()
|
||||
if (iterations == 0) {
|
||||
errors.warn("iterations is always 0, removed loop", iter.position)
|
||||
return listOf(IAstModification.Remove(repeatLoop, parent))
|
||||
}
|
||||
if (iterations == 1) {
|
||||
errors.warn("iterations is always 1", iter.position)
|
||||
return listOf(IAstModification.ReplaceNode(repeatLoop, repeatLoop.body, parent))
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
|
||||
// remove empty choices
|
||||
class ChoiceRemover(val choice: WhenChoice) : IAstModification {
|
||||
override fun perform() {
|
||||
whenStatement.choices.remove(choice)
|
||||
}
|
||||
}
|
||||
return whenStatement.choices
|
||||
.filter { !it.statements.containsCodeOrVars() }
|
||||
.map { ChoiceRemover(it) }
|
||||
}
|
||||
|
||||
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.definingScope()
|
||||
val label = jump.identifier?.targetStatement(scope)
|
||||
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
|
||||
return listOf(IAstModification.Remove(jump, parent))
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if(binExpr!=null) {
|
||||
if(binExpr.left isSameAs assignment.target) {
|
||||
val rExpr = binExpr.right as? BinaryExpression
|
||||
if(rExpr!=null) {
|
||||
val op1 = binExpr.operator
|
||||
val op2 = rExpr.operator
|
||||
|
||||
if(rExpr.left is NumericLiteralValue && op2 in setOf("+", "*", "&", "|")) {
|
||||
// associative operator, make sure the constant numeric value is second (right)
|
||||
return listOf(IAstModification.SwapOperands(rExpr))
|
||||
}
|
||||
|
||||
val rNum = (rExpr.right as? NumericLiteralValue)?.number
|
||||
if(rNum!=null) {
|
||||
if (op1 == "+" || op1 == "-") {
|
||||
if (op2 == "+") {
|
||||
// A = A +/- B + N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val addConstant = Assignment(
|
||||
assignment.target,
|
||||
BinaryExpression(binExpr.left, "+", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, addConstant, parent))
|
||||
} else if (op2 == "-") {
|
||||
// A = A +/- B - N
|
||||
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
|
||||
val subConstant = Assignment(
|
||||
assignment.target,
|
||||
BinaryExpression(binExpr.left, "-", rExpr.right, rExpr.position),
|
||||
assignment.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
|
||||
IAstModification.InsertAfter(assignment, subConstant, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(binExpr.operator in associativeOperators && binExpr.right isSameAs assignment.target) {
|
||||
// associative operator, swap the operands so that the assignment target is first (left)
|
||||
// unless the other operand is the same in which case we don't swap (endless loop!)
|
||||
if (!(binExpr.left isSameAs binExpr.right))
|
||||
return listOf(IAstModification.SwapOperands(binExpr))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
// remove assignment to self
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
}
|
||||
|
||||
val targetIDt = assignment.target.inferType(program, assignment)
|
||||
if(!targetIDt.isKnown)
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
|
||||
// optimize binary expressions a bit
|
||||
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (cv != null && assignment.target isSameAs bexpr.left) {
|
||||
// assignments of the form: X = X <operator> <expr>
|
||||
// remove assignments that have no effect (such as X=X+0)
|
||||
// optimize/rewrite some other expressions
|
||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
if (cv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..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()) {
|
||||
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if (cv == 0.0) {
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..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()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
||||
}
|
||||
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"/" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"**" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"|" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"^" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
|
||||
"<<" -> {
|
||||
if (cv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
}
|
||||
">>" -> {
|
||||
if (cv == 0.0)
|
||||
return listOf(IAstModification.Remove(assignment, parent))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
|
||||
@ -155,223 +475,17 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return linesToRemove
|
||||
}
|
||||
|
||||
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
|
||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in pureBuiltinFunctions) {
|
||||
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(functionCallStatement)
|
||||
}
|
||||
}
|
||||
private fun hasBreak(scope: INameScope): Boolean {
|
||||
|
||||
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
|
||||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
|
||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||
val arg = functionCallStatement.args.single()
|
||||
val stringVar: IdentifierReference?
|
||||
stringVar = if(arg is AddressOf) {
|
||||
arg.identifier
|
||||
} else {
|
||||
arg as? IdentifierReference
|
||||
}
|
||||
if(stringVar!=null) {
|
||||
val vardecl = stringVar.targetVarDecl(program.namespace)!!
|
||||
val string = vardecl.value!! as StringLiteralValue
|
||||
if(string.value.length==1) {
|
||||
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
|
||||
functionCallStatement.args.clear()
|
||||
functionCallStatement.args.add(NumericLiteralValue.optimalInteger(firstCharEncoded.toInt(), functionCallStatement.position))
|
||||
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
|
||||
vardeclsToRemove.add(vardecl)
|
||||
optimizationsDone++
|
||||
return functionCallStatement
|
||||
} else if(string.value.length==2) {
|
||||
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
|
||||
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||
mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[0].toInt(), functionCallStatement.position)),
|
||||
functionCallStatement.void, functionCallStatement.position))
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||
mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[1].toInt(), functionCallStatement.position)),
|
||||
functionCallStatement.void, functionCallStatement.position))
|
||||
vardeclsToRemove.add(vardecl)
|
||||
optimizationsDone++
|
||||
return scope
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it calls a subroutine,
|
||||
// and the first instruction in the subroutine is a jump, call that jump target instead
|
||||
// if the first instruction in the subroutine is a return statement, replace with a nop instruction
|
||||
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump && first.identifier!=null) {
|
||||
optimizationsDone++
|
||||
return FunctionCallStatement(first.identifier, functionCallStatement.args, functionCallStatement.void, functionCallStatement.position)
|
||||
}
|
||||
if(first is ReturnFromIrq || first is Return) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(functionCallStatement)
|
||||
}
|
||||
}
|
||||
|
||||
return super.visit(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun visit(functionCall: FunctionCall): Expression {
|
||||
// if it calls a subroutine,
|
||||
// and the first instruction in the subroutine is a jump, call that jump target instead
|
||||
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump && first.identifier!=null) {
|
||||
optimizationsDone++
|
||||
return FunctionCall(first.identifier, functionCall.args, functionCall.position)
|
||||
}
|
||||
if(first is Return && first.value!=null) {
|
||||
val constval = first.value?.constValue(program)
|
||||
if(constval!=null)
|
||||
return constval
|
||||
}
|
||||
}
|
||||
return super.visit(functionCall)
|
||||
}
|
||||
|
||||
override fun visit(ifStatement: IfStatement): Statement {
|
||||
super.visit(ifStatement)
|
||||
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(ifStatement)
|
||||
}
|
||||
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
|
||||
// invert the condition and move else part to true part
|
||||
ifStatement.truepart = ifStatement.elsepart
|
||||
ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||
ifStatement.condition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
|
||||
optimizationsDone++
|
||||
return ifStatement
|
||||
}
|
||||
|
||||
val constvalue = ifStatement.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
errors.warn("condition is always true", ifStatement.position)
|
||||
optimizationsDone++
|
||||
ifStatement.truepart
|
||||
} else {
|
||||
// always false -> keep only else-part
|
||||
errors.warn("condition is always false", ifStatement.position)
|
||||
optimizationsDone++
|
||||
ifStatement.elsepart
|
||||
}
|
||||
}
|
||||
return ifStatement
|
||||
}
|
||||
|
||||
override fun visit(forLoop: ForLoop): Statement {
|
||||
super.visit(forLoop)
|
||||
if(forLoop.body.containsNoCodeNorVars()) {
|
||||
// remove empty for loop
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(forLoop)
|
||||
} else if(forLoop.body.statements.size==1) {
|
||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
|
||||
// remove empty for loop
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(forLoop)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
if(range!=null) {
|
||||
if(range.size()==1) {
|
||||
// for loop over a (constant) range of just a single value-- optimize the loop away
|
||||
// loopvar/reg = range value , follow by block
|
||||
val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position)
|
||||
forLoop.body.statements.add(0, assignment)
|
||||
optimizationsDone++
|
||||
return forLoop.body
|
||||
}
|
||||
}
|
||||
return forLoop
|
||||
}
|
||||
|
||||
override fun visit(whileLoop: WhileLoop): Statement {
|
||||
super.visit(whileLoop)
|
||||
val constvalue = whileLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> print a warning, and optimize into a forever-loop
|
||||
errors.warn("condition is always true", whileLoop.condition.position)
|
||||
optimizationsDone++
|
||||
ForeverLoop(whileLoop.body, whileLoop.position)
|
||||
} else {
|
||||
// always false -> remove the while statement altogether
|
||||
errors.warn("condition is always false", whileLoop.condition.position)
|
||||
optimizationsDone++
|
||||
NopStatement.insteadOf(whileLoop)
|
||||
}
|
||||
}
|
||||
return whileLoop
|
||||
}
|
||||
|
||||
override fun visit(repeatLoop: RepeatLoop): Statement {
|
||||
super.visit(repeatLoop)
|
||||
val constvalue = repeatLoop.untilCondition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only the statement block (if there are no continue and break statements)
|
||||
errors.warn("condition is always true", repeatLoop.untilCondition.position)
|
||||
if(hasContinueOrBreak(repeatLoop.body))
|
||||
repeatLoop
|
||||
else {
|
||||
optimizationsDone++
|
||||
repeatLoop.body
|
||||
}
|
||||
} else {
|
||||
// always false -> print a warning, and optimize into a forever loop
|
||||
errors.warn("condition is always false", repeatLoop.untilCondition.position)
|
||||
optimizationsDone++
|
||||
ForeverLoop(repeatLoop.body, repeatLoop.position)
|
||||
}
|
||||
}
|
||||
return repeatLoop
|
||||
}
|
||||
|
||||
override fun visit(whenStatement: WhenStatement): Statement {
|
||||
val choices = whenStatement.choices.toList()
|
||||
for(choice in choices) {
|
||||
if(choice.statements.containsNoCodeNorVars())
|
||||
whenStatement.choices.remove(choice)
|
||||
}
|
||||
return super.visit(whenStatement)
|
||||
}
|
||||
|
||||
private fun hasContinueOrBreak(scope: INameScope): Boolean {
|
||||
|
||||
class Searcher: IAstModifyingVisitor
|
||||
class Searcher: IAstVisitor
|
||||
{
|
||||
var count=0
|
||||
|
||||
override fun visit(breakStmt: Break): Statement {
|
||||
override fun visit(breakStmt: Break) {
|
||||
count++
|
||||
return super.visit(breakStmt)
|
||||
}
|
||||
|
||||
override fun visit(contStmt: Continue): Statement {
|
||||
count++
|
||||
return super.visit(contStmt)
|
||||
}
|
||||
}
|
||||
|
||||
val s=Searcher()
|
||||
for(stmt in scope.statements) {
|
||||
stmt.accept(s)
|
||||
@ -381,185 +495,4 @@ internal class StatementOptimizer(private val program: Program,
|
||||
return s.count > 0
|
||||
}
|
||||
|
||||
override fun visit(jump: Jump): Statement {
|
||||
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
// if the first instruction in the subroutine is another jump, shortcut this one
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump) {
|
||||
optimizationsDone++
|
||||
return first
|
||||
}
|
||||
}
|
||||
|
||||
// if the jump is to the next statement, remove the jump
|
||||
val scope = jump.definingScope()
|
||||
val label = jump.identifier?.targetStatement(scope)
|
||||
if(label!=null) {
|
||||
if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(jump)
|
||||
}
|
||||
}
|
||||
|
||||
return jump
|
||||
}
|
||||
|
||||
override fun visit(assignment: Assignment): Statement {
|
||||
if(assignment.aug_op!=null)
|
||||
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
|
||||
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
if(assignment.target.isNotMemory(program.namespace)) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
}
|
||||
val targetIDt = assignment.target.inferType(program, assignment)
|
||||
if(!targetIDt.isKnown)
|
||||
throw FatalAstException("can't infer type of assignment target")
|
||||
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val cv = bexpr.right.constValue(program)?.number?.toDouble()
|
||||
if (cv == null) {
|
||||
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
|
||||
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
|
||||
bexpr.operator = "*"
|
||||
bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
|
||||
optimizationsDone++
|
||||
return assignment
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (assignment.target isSameAs bexpr.left) {
|
||||
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
|
||||
// A = A <operator> B
|
||||
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
|
||||
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
||||
// replace by several INCs (a bit less when dealing with memory targets)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
|
||||
}
|
||||
return decs
|
||||
}
|
||||
}
|
||||
}
|
||||
"-" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
|
||||
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
|
||||
// replace by several DECs (a bit less when dealing with memory targets)
|
||||
val decs = AnonymousScope(mutableListOf(), assignment.position)
|
||||
repeat(cv.toInt()) {
|
||||
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
|
||||
}
|
||||
return decs
|
||||
}
|
||||
}
|
||||
}
|
||||
"*" -> if (cv == 1.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"/" -> if (cv == 1.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"**" -> if (cv == 1.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"|" -> if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"^" -> if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
"<<" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
|
||||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
|
||||
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
|
||||
assignment.value.linkParents(assignment)
|
||||
optimizationsDone++
|
||||
} else {
|
||||
// replace by in-place lsl(...) call
|
||||
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
||||
var numshifts = cv.toInt()
|
||||
while (numshifts > 0) {
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
|
||||
mutableListOf(bexpr.left), true, assignment.position))
|
||||
numshifts--
|
||||
}
|
||||
optimizationsDone++
|
||||
return scope
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
if (cv == 0.0) {
|
||||
optimizationsDone++
|
||||
return NopStatement.insteadOf(assignment)
|
||||
}
|
||||
if ((targetDt == DataType.UWORD && cv > 15.0) || (targetDt == DataType.UBYTE && cv > 7.0)) {
|
||||
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
|
||||
assignment.value.linkParents(assignment)
|
||||
optimizationsDone++
|
||||
} else {
|
||||
// replace by in-place lsr(...) call
|
||||
val scope = AnonymousScope(mutableListOf(), assignment.position)
|
||||
var numshifts = cv.toInt()
|
||||
while (numshifts > 0) {
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
|
||||
mutableListOf(bexpr.left), true, assignment.position))
|
||||
numshifts--
|
||||
}
|
||||
optimizationsDone++
|
||||
return scope
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.visit(assignment)
|
||||
}
|
||||
|
||||
override fun visit(scope: AnonymousScope): Statement {
|
||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||
if(linesToRemove.isNotEmpty()) {
|
||||
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
|
||||
}
|
||||
return super.visit(scope)
|
||||
}
|
||||
|
||||
override fun visit(label: Label): Statement {
|
||||
// remove duplicate labels
|
||||
val stmts = label.definingScope().statements
|
||||
val startIdx = stmts.indexOf(label)
|
||||
if(startIdx< stmts.lastIndex && stmts[startIdx+1] == label)
|
||||
return NopStatement.insteadOf(label)
|
||||
|
||||
return super.visit(label)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
69
compiler/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
69
compiler/src/prog8/optimizer/UnusedCodeRemover.kt
Normal file
@ -0,0 +1,69 @@
|
||||
package prog8.optimizer
|
||||
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.processing.AstWalker
|
||||
import prog8.ast.processing.IAstModification
|
||||
import prog8.ast.statements.*
|
||||
|
||||
internal class UnusedCodeRemover(private val errors: ErrorReporter): AstWalker() {
|
||||
|
||||
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
|
||||
val callgraph = CallGraph(program)
|
||||
val removals = mutableListOf<IAstModification>()
|
||||
|
||||
// remove all subroutines that aren't called, or are empty
|
||||
val entrypoint = program.entrypoint()
|
||||
program.modules.forEach {
|
||||
callgraph.forAllSubroutines(it) { sub ->
|
||||
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
|
||||
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
|
||||
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
|
||||
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
|
||||
}
|
||||
|
||||
// remove modules that are not imported, or are empty (unless it's a library modules)
|
||||
program.modules.forEach {
|
||||
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
|
||||
removals.add(IAstModification.Remove(it, it.parent))
|
||||
}
|
||||
|
||||
return removals
|
||||
}
|
||||
|
||||
|
||||
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(breakStmt, parent as INameScope)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(jump, parent as INameScope)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
|
||||
reportUnreachable(returnStmt, parent as INameScope)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
|
||||
if(functionCallStatement.target.nameInSource.last() == "exit")
|
||||
reportUnreachable(functionCallStatement, parent as INameScope)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
|
||||
when(val next = parent.nextSibling(stmt)) {
|
||||
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine, is StructDecl -> {}
|
||||
else -> errors.warn("unreachable code", next.position)
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import org.antlr.v4.runtime.*
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.antlr.toAst
|
||||
import prog8.ast.base.ErrorReporter
|
||||
import prog8.ast.base.Position
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.base.checkImportedValid
|
||||
@ -34,7 +33,7 @@ internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexe
|
||||
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
|
||||
|
||||
|
||||
internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
internal class ModuleImporter {
|
||||
|
||||
internal fun importModule(program: Program, filePath: Path): Module {
|
||||
print("importing '${moduleName(filePath.fileName)}'")
|
||||
@ -95,7 +94,7 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
|
||||
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
||||
val fileName = "$name.p8"
|
||||
val locations = mutableListOf(source.parent)
|
||||
val locations = if(source.toString().isEmpty()) mutableListOf<Path>() else mutableListOf(source.parent)
|
||||
|
||||
val propPath = System.getProperty("prog8.libdir")
|
||||
if(propPath!=null)
|
||||
@ -110,7 +109,7 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
if (Files.isReadable(file)) return file
|
||||
}
|
||||
|
||||
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
|
||||
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: embedded libs and $locations)")
|
||||
}
|
||||
|
||||
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
|
||||
@ -129,10 +128,7 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
|
||||
if(resource!=null) {
|
||||
// load the module from the embedded resource
|
||||
resource.use {
|
||||
if(import.args[0].int==42)
|
||||
println("importing '$moduleName' (library, auto)")
|
||||
else
|
||||
println("importing '$moduleName' (library)")
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
|
||||
}
|
||||
} else {
|
||||
|
@ -123,7 +123,7 @@ class TestCompiler {
|
||||
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class TestZeropage {
|
||||
class TestC64Zeropage {
|
||||
|
||||
private val errors = ErrorReporter()
|
||||
|
||||
|
@ -83,7 +83,7 @@ For normal use the compiler is invoked with the command:
|
||||
By default, assembly code is generated and written to ``sourcefile.asm``.
|
||||
It is then (automatically) fed to the `64tass <https://sourceforge.net/projects/tass64/>`_ cross assembler tool
|
||||
that assembles it into the final program.
|
||||
If you use the option to let the compiler auto-start a C-64 emulator, it will do so after
|
||||
If you use the option to let the compiler auto-start an emulator, it will do so after
|
||||
a successful compilation. This will load your program and the symbol and breakpoint lists
|
||||
(for the machine code monitor) into the emulator.
|
||||
|
||||
@ -109,8 +109,8 @@ A module source file is a text file with the ``.p8`` suffix, containing the prog
|
||||
It consists of compilation options and other directives, imports of other modules,
|
||||
and source code for one or more code blocks.
|
||||
|
||||
Prog8 has a couple of *LIBRARY* modules that are defined in special internal files provided by the compiler:
|
||||
``c64lib``, ``c64utils``, ``c64flt`` and ``prog8lib``. You should not overwrite these or reuse their names.
|
||||
Prog8 has various *LIBRARY* modules that are defined in special internal files provided by the compiler.
|
||||
You should not overwrite these or reuse their names.
|
||||
They are embedded into the packaged release version of the compiler so you don't have to worry about
|
||||
where they are, but their names are still reserved.
|
||||
|
||||
|
@ -12,6 +12,7 @@ What is Prog8?
|
||||
|
||||
This is an experimental compiled programming language targeting the 8-bit
|
||||
`6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
|
||||
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ /
|
||||
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ microprocessor.
|
||||
This CPU is from the late 1970's and early 1980's and was used in many home computers from that era,
|
||||
such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
|
||||
@ -42,7 +43,7 @@ Code examples
|
||||
|
||||
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -51,35 +52,33 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
ubyte candidate_prime = 2
|
||||
|
||||
sub start() {
|
||||
memset(sieve, 256, false)
|
||||
|
||||
c64scr.print("prime numbers up to 255:\n\n")
|
||||
memset(sieve, 256, false) ; clear the sieve
|
||||
txt.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
while true {
|
||||
repeat {
|
||||
ubyte prime = find_next_prime()
|
||||
if prime==0
|
||||
break
|
||||
c64scr.print_ub(prime)
|
||||
c64scr.print(", ")
|
||||
txt.print_ub(prime)
|
||||
txt.print(", ")
|
||||
amount++
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("number of primes (expected 54): ")
|
||||
c64scr.print_ub(amount)
|
||||
txt.print("number of primes (expected 54): ")
|
||||
txt.print_ub(amount)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
|
||||
sub find_next_prime() -> ubyte {
|
||||
|
||||
while sieve[candidate_prime] {
|
||||
candidate_prime++
|
||||
if candidate_prime==0
|
||||
return 0
|
||||
return 0 ; we wrapped; no more primes available
|
||||
}
|
||||
|
||||
; 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
|
||||
@ -89,6 +88,7 @@ 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
|
||||
@ -98,7 +98,8 @@ when compiled an ran on a C-64 you get this:
|
||||
|
||||
The following programs shows a use of the high level ``struct`` type::
|
||||
|
||||
%import c64utils
|
||||
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -111,22 +112,25 @@ The following programs shows a use of the high level ``struct`` type::
|
||||
|
||||
sub start() {
|
||||
|
||||
Color purple = {255, 0, 255}
|
||||
Color purple = [255, 0, 255]
|
||||
|
||||
Color other
|
||||
|
||||
other = purple
|
||||
other.red /= 2
|
||||
other.green = 10 + other.green / 2
|
||||
other.blue = 99
|
||||
|
||||
c64scr.print_ub(other.red)
|
||||
txt.print_ub(other.red)
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_ub(other.green)
|
||||
txt.print_ub(other.green)
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_ub(other.blue)
|
||||
txt.print_ub(other.blue)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
when compiled and ran, it prints ``127,10,99`` on the screen.
|
||||
|
||||
|
||||
@ -139,8 +143,8 @@ Design principles and features
|
||||
- 'One statement per line' code, resulting in clear readable programs.
|
||||
- Modular programming and scoping via modules, code blocks, and subroutines.
|
||||
- Provide high level programming constructs but at the same time stay close to the metal;
|
||||
still able to directly use memory addresses, CPU registers and ROM subroutines,
|
||||
and inline assembly to have full control when every cycle or byte matters
|
||||
still able to directly use memory addresses and ROM subroutines,
|
||||
and inline assembly to have full control when every register, cycle or byte matters
|
||||
- Arbitrary number of subroutine parameters
|
||||
- Complex nested expressions are possible
|
||||
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
|
||||
@ -156,7 +160,7 @@ Design principles and features
|
||||
- The compiler tries to optimize the program and generated code a bit, but hand-tuning of the
|
||||
performance or space-critical parts will likely still be required. This is supported by
|
||||
the ability to easily write embedded assembly code directly in the program source code.
|
||||
- There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
|
||||
- There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``substr``, ``sort`` and ``reverse`` (and others)
|
||||
- Assembling the generated code into a program wil be done by an external cross-assembler tool.
|
||||
|
||||
|
||||
@ -175,6 +179,7 @@ Fnd for Windows it's possible to get that as well. Check out `AdoptOpenJDK <http
|
||||
|
||||
Finally: a **C-64 emulator** (or a real C-64 ofcourse) can be nice to test and run your programs on.
|
||||
The compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
If you're targeting the CommanderX16, there's the `x16emu <https://github.com/commanderx16/x16-emulator>`_.
|
||||
|
||||
.. important::
|
||||
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
|
||||
|
@ -50,7 +50,7 @@ Code
|
||||
There are different kinds of instructions ('statements' is a better name) such as:
|
||||
|
||||
- value assignment
|
||||
- looping (for, while, repeat, unconditional jumps)
|
||||
- looping (for, while, do-until, repeat, unconditional jumps)
|
||||
- conditional execution (if - then - else, when, and conditional jumps)
|
||||
- subroutine calls
|
||||
- label definition
|
||||
@ -137,7 +137,7 @@ Scopes are created using either of these two statements:
|
||||
|
||||
.. important::
|
||||
Unlike most other programming languages, a new scope is *not* created inside
|
||||
for, while and repeat statements, the if statement, and the branching conditionals.
|
||||
for, while, repeat, and do-until statements, the if statement, and the branching conditionals.
|
||||
These all share the same scope from the subroutine they're defined in.
|
||||
You can define variables in these blocks, but these will be treated as if they
|
||||
were defined in the subroutine instead.
|
||||
@ -204,13 +204,6 @@ Example::
|
||||
byte @zp zeropageCounter = 42
|
||||
|
||||
|
||||
Variables that represent CPU hardware registers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following variables are reserved
|
||||
and map directly (read/write) to a CPU hardware register: ``A``, ``X``, ``Y``.
|
||||
|
||||
|
||||
Integers
|
||||
^^^^^^^^
|
||||
|
||||
@ -264,6 +257,16 @@ Note that the various keywords for the data type and variable type (``byte``, ``
|
||||
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
|
||||
for instance.
|
||||
|
||||
**Arrays at a specific memory location:**
|
||||
Using the memory-mapped syntax it is possible to define an array to be located at a specific memory location.
|
||||
For instance to reference the first 5 rows of the Commodore 64's screen matrix as an array, you can define::
|
||||
|
||||
&ubyte[5*40] top5screenrows = $0400
|
||||
|
||||
This way you can set the second character on the second row from the top like this::
|
||||
|
||||
top5screenrows[41] = '!'
|
||||
|
||||
|
||||
Strings
|
||||
^^^^^^^
|
||||
@ -279,16 +282,23 @@ This @-prefix can also be used for character byte values.
|
||||
|
||||
|
||||
You can concatenate two string literals using '+' (not very useful though) or repeat
|
||||
a string literal a given number of times using '*'::
|
||||
a string literal a given number of times using '*'. You can also assign a new string
|
||||
value to another string. No bounds check is done so be sure the destination string is
|
||||
large enough to contain the new value::
|
||||
|
||||
str string1 = "first part" + "second part"
|
||||
str string2 = "hello!" * 10
|
||||
|
||||
string1 = string2
|
||||
string1 = "new value"
|
||||
|
||||
|
||||
.. caution::
|
||||
Avoid changing strings after they've been created.
|
||||
It's probably best to avoid changing strings after they've been created. This
|
||||
includes changing certain letters by index, or by assigning a new value, or by
|
||||
modifying the string via other means for example ``substr`` function and its cousins.
|
||||
This is because if your program exits and is restarted (without loading it again),
|
||||
it will then start working with the changed strings instead of the original ones.
|
||||
it will then start working with the changed strings instead of the original ones!
|
||||
The same is true for arrays.
|
||||
|
||||
|
||||
@ -386,21 +396,21 @@ expected when the program is restarted.
|
||||
Loops
|
||||
-----
|
||||
|
||||
The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
||||
The *for*-loop is used to let a variable iterate over a range of values. Iteration is done in steps of 1, but you can change this.
|
||||
The loop variable must be declared as byte or word earlier so you can reuse it for multiple occasions.
|
||||
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
|
||||
|
||||
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
|
||||
The *repeat--until* loop is used to repeat a piece of code until a certain condition is true.
|
||||
|
||||
The *forever*-loop is used to simply run a piece of code in a loop, forever. You can still
|
||||
break out of this loop if desired. A "while true" or "until false" loop is equivalent to
|
||||
a forever-loop.
|
||||
The *do--until* loop is used to repeat a piece of code until a certain condition is true.
|
||||
The *repeat* loop is used as a short notation of a for loop where the loop variable doesn't matter and you're only interested in the number of iterations.
|
||||
(without iteration count specified it simply loops forever).
|
||||
|
||||
You can also create loops by using the ``goto`` statement, but this should usually be avoided.
|
||||
|
||||
Breaking out of a loop prematurely is possible with the ``break`` statement.
|
||||
|
||||
.. attention::
|
||||
The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately
|
||||
The value of the loop variable after executing the loop *is undefined*. Don't use it immediately
|
||||
after the loop without first assigning a new value to it!
|
||||
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
|
||||
|
||||
@ -414,15 +424,15 @@ if statements
|
||||
Conditional execution means that the flow of execution changes based on certiain conditions,
|
||||
rather than having fixed gotos or subroutine calls::
|
||||
|
||||
if A>4 goto overflow
|
||||
if aa>4 goto overflow
|
||||
|
||||
if X==3 Y = 4
|
||||
if X==3 Y = 4 else A = 2
|
||||
if xx==3 yy = 4
|
||||
if xx==3 yy = 4 else aa = 2
|
||||
|
||||
if X==5 {
|
||||
Y = 99
|
||||
if xx==5 {
|
||||
yy = 99
|
||||
} else {
|
||||
A = 3
|
||||
aa = 3
|
||||
}
|
||||
|
||||
|
||||
@ -486,16 +496,16 @@ Assignments
|
||||
-----------
|
||||
|
||||
Assignment statements assign a single value to a target variable or memory location.
|
||||
Augmented assignments (such as ``A += X``) are also available, but these are just shorthands
|
||||
for normal assignments (``A = A + X``).
|
||||
Augmented assignments (such as ``aa += xx``) are also available, but these are just shorthands
|
||||
for normal assignments (``aa = aa + xx``).
|
||||
|
||||
Only register variables and variables of type byte, word and float can be assigned a new value.
|
||||
Only variables of type byte, word and float can be assigned a new value.
|
||||
It's not possible to set a new value to string or array variables etc, because they get allocated
|
||||
a fixed amount of memory which will not change.
|
||||
a fixed amount of memory which will not change. (You *can* change the value of elements in a string or array though).
|
||||
|
||||
.. attention::
|
||||
**Data type conversion (in assignments):**
|
||||
When assigning a value with a 'smaller' datatype to a register or variable with a 'larger' datatype,
|
||||
When assigning a value with a 'smaller' datatype to variable with a 'larger' datatype,
|
||||
the value will be automatically converted to the target datatype: byte --> word --> float.
|
||||
So assigning a byte to a word variable, or a word to a floating point variable, is fine.
|
||||
The reverse is *not* true: it is *not* possible to assign a value of a 'larger' datatype to
|
||||
@ -511,7 +521,7 @@ as the memory mapped address $d021.
|
||||
If you want to access a memory location directly (by using the address itself), without defining
|
||||
a memory mapped location, you can do so by enclosing the address in ``@(...)``::
|
||||
|
||||
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)")
|
||||
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
|
||||
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
||||
@(vic+$20) = 6 ; you can also use expressions to 'calculate' the address
|
||||
|
||||
@ -714,11 +724,17 @@ reverse(array)
|
||||
|
||||
len(x)
|
||||
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
|
||||
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte.
|
||||
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. See sizeof().
|
||||
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
|
||||
length of the string during execution, the value of len(string) may no longer be correct!
|
||||
(use strlen function if you want to dynamically determine the length)
|
||||
|
||||
sizeof(name)
|
||||
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
|
||||
the object. For instance, for a variable of type uword, the sizeof is 2.
|
||||
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
|
||||
Note: usually you will be interested in the number of elements in an array, use len() for that.
|
||||
|
||||
strlen(str)
|
||||
Number of bytes in the string. This value is determined during runtime and counts upto
|
||||
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
|
||||
@ -732,8 +748,9 @@ msb(x)
|
||||
sgn(x)
|
||||
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
|
||||
|
||||
mkword(lsb, msb)
|
||||
Efficiently create a word value from two bytes (the lsb and the msb). Avoids multiplication and shifting.
|
||||
mkword(msb, lsb)
|
||||
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
|
||||
So mkword($80, $22) results in $8022.
|
||||
|
||||
any(x)
|
||||
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
|
||||
@ -750,16 +767,6 @@ rndw()
|
||||
rndf()
|
||||
returns a pseudo-random float between 0.0 and 1.0
|
||||
|
||||
lsl(x)
|
||||
Shift the bits in x (byte or word) one position to the left.
|
||||
Bit 0 is set to 0 (and the highest bit is shifted into the status register's Carry flag)
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
lsr(x)
|
||||
Shift the bits in x (byte or word) one position to the right.
|
||||
The highest bit is set to 0 (and bit 0 is shifted into the status register's Carry flag)
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
rol(x)
|
||||
Rotate the bits in x (byte or word) one position to the left.
|
||||
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
|
||||
@ -796,12 +803,28 @@ 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 ``c64scr`` block (part of the ``c64utils`` module)
|
||||
available in the ``screen`` block (part of the ``c64textio`` or ``cx16textio`` modules)
|
||||
|
||||
memsetw(address, numwords, wordvalue)
|
||||
Efficiently set a part of memory to the given (u)word value.
|
||||
But the most efficient will always be to write a specialized fill routine in assembly yourself!
|
||||
|
||||
leftstr(source, target, length)
|
||||
Copies the left side of the source string of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
rightstr(source, target, length)
|
||||
Copies the right side of the source string of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
substr(source, target, start, length)
|
||||
Copies a segment from the source string, starting at the given index,
|
||||
and of the given length to target string.
|
||||
It is assumed the target string buffer is large enough to contain the result.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
|
||||
swap(x, y)
|
||||
Swap the values of numerical variables (or memory locations) x and y in a fast way.
|
||||
|
||||
|
@ -24,7 +24,7 @@ Everything after a semicolon ``;`` is a comment and is ignored.
|
||||
If the whole line is just a comment, it will be copied into the resulting assembly source code.
|
||||
This makes it easier to understand and relate the generated code. Examples::
|
||||
|
||||
A = 42 ; set the initial value to 42
|
||||
counter = 42 ; set the initial value to 42
|
||||
; next is the code that...
|
||||
|
||||
|
||||
@ -157,7 +157,7 @@ Directives
|
||||
Identifiers
|
||||
-----------
|
||||
|
||||
Naming things in Prog8 is done via valid *identifiers*. They start with a letter or underscore,
|
||||
Naming things in Prog8 is done via valid *identifiers*. They start with a letter,
|
||||
and after that, a combination of letters, numbers, or underscores. Examples of valid identifiers::
|
||||
|
||||
a
|
||||
@ -165,7 +165,7 @@ and after that, a combination of letters, numbers, or underscores. Examples of v
|
||||
monkey
|
||||
COUNTER
|
||||
Better_Name_2
|
||||
_something_strange_
|
||||
something_strange__
|
||||
|
||||
|
||||
Code blocks
|
||||
@ -306,6 +306,7 @@ should be allocated by the compiler. Instead, the (mandatory) value assigned to
|
||||
should be the *memory address* where the value is located::
|
||||
|
||||
&byte BORDERCOLOR = $d020
|
||||
&ubyte[5*40] top5screenrows = $0400 ; works for array as well
|
||||
|
||||
|
||||
Direct access to memory locations
|
||||
@ -313,7 +314,7 @@ Direct access to memory locations
|
||||
Instead of defining a memory mapped name for a specific memory location, you can also
|
||||
directly access the memory. Enclose a numeric expression or literal with ``@(...)`` to do that::
|
||||
|
||||
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)")
|
||||
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
|
||||
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
|
||||
@(vic+$20) = 6 ; a dynamic expression to 'calculate' the address
|
||||
|
||||
@ -333,8 +334,6 @@ Reserved names
|
||||
|
||||
The following names are reserved, they have a special meaning::
|
||||
|
||||
A X Y ; 6502 hardware registers
|
||||
Pc Pz Pn Pv ; 6502 status register flags
|
||||
true false ; boolean values 1 and 0
|
||||
|
||||
|
||||
@ -406,10 +405,10 @@ assignment: ``=``
|
||||
Note that an assignment sometimes is not possible or supported.
|
||||
|
||||
augmented assignment: ``+=`` ``-=`` ``*=`` ``/=`` ``**=`` ``&=`` ``|=`` ``^=`` ``<<=`` ``>>=``
|
||||
Syntactic sugar; ``A += X`` is equivalent to ``A = A + X``
|
||||
This is syntactic sugar; ``aa += xx`` is equivalent to ``aa = aa + xx``
|
||||
|
||||
postfix increment and decrement: ``++`` ``--``
|
||||
Syntactic sugar; ``A++`` is equivalent to ``A = A + 1``, and ``A--`` is equivalent to ``A = A - 1``.
|
||||
Syntactic sugar; ``aa++`` is equivalent to ``aa = aa + 1``, and ``aa--`` is equivalent to ``aa = aa - 1``.
|
||||
Because these operations are so common, we have these short forms.
|
||||
|
||||
comparison: ``!=`` ``<`` ``>`` ``<=`` ``>=``
|
||||
@ -427,9 +426,9 @@ range creation: ``to``
|
||||
|
||||
0 to 7 ; range of values 0, 1, 2, 3, 4, 5, 6, 7 (constant)
|
||||
|
||||
A = 5
|
||||
X = 10
|
||||
A to X ; range of 5, 6, 7, 8, 9, 10
|
||||
aa = 5
|
||||
aa = 10
|
||||
aa to xx ; range of 5, 6, 7, 8, 9, 10
|
||||
|
||||
byte[] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
|
||||
|
||||
@ -551,7 +550,7 @@ Loops
|
||||
for loop
|
||||
^^^^^^^^
|
||||
|
||||
The loop variable must be a register or a byte/word variable,
|
||||
The loop variable must be a byte or word variable,
|
||||
and must be defined first in the local scope of the for loop.
|
||||
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
|
||||
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
|
||||
@ -561,7 +560,6 @@ You can use a single statement, or a statement block like in the example below::
|
||||
for <loopvar> in <expression> [ step <amount> ] {
|
||||
; do something...
|
||||
break ; break out of the loop
|
||||
continue ; immediately enter next iteration
|
||||
}
|
||||
|
||||
For example, this is a for loop using a byte variable ``i``, defined before, to loop over a certain range of numbers::
|
||||
@ -594,35 +592,35 @@ You can use a single statement, or a statement block like in the example below::
|
||||
while <condition> {
|
||||
; do something...
|
||||
break ; break out of the loop
|
||||
continue ; immediately enter next iteration
|
||||
}
|
||||
|
||||
|
||||
repeat-until loop
|
||||
^^^^^^^^^^^^^^^^^
|
||||
do-until loop
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Until the given condition is true (1), repeat the given statement(s).
|
||||
You can use a single statement, or a statement block like in the example below::
|
||||
|
||||
repeat {
|
||||
do {
|
||||
; do something...
|
||||
break ; break out of the loop
|
||||
continue ; immediately enter next iteration
|
||||
} until <condition>
|
||||
|
||||
|
||||
forever loop
|
||||
^^^^^^^^^^^^
|
||||
repeat loop
|
||||
^^^^^^^^^^^
|
||||
|
||||
Simply run the code in a loop, forever. It's the same as a while true or until false loop,
|
||||
or just a jump back to a previous label. You can still break out of this loop as well, if you want::
|
||||
When you're only interested in repeating something a given number of times.
|
||||
It's a short hand for a for loop without an explicit loop variable::
|
||||
|
||||
forever {
|
||||
; .. do stuff
|
||||
if something
|
||||
break ; you can exit the loop if you want
|
||||
repeat 15 {
|
||||
; do something...
|
||||
break ; you can break out of the loop
|
||||
}
|
||||
|
||||
If you omit the iteration count, it simply loops forever.
|
||||
You can still ``break`` out of such a loop if you want though.
|
||||
|
||||
|
||||
Conditional Execution and Jumps
|
||||
-------------------------------
|
||||
@ -702,3 +700,4 @@ case you have to use { } to enclose them::
|
||||
}
|
||||
else -> c64scr.print("don't know")
|
||||
}
|
||||
|
||||
|
@ -4,12 +4,17 @@ Target system specification
|
||||
|
||||
Prog8 targets the following hardware:
|
||||
|
||||
- 8 bit MOS 6502/6510 CPU
|
||||
- 8 bit MOS 6502/65c02/6510 CPU
|
||||
- 64 Kb addressable memory (RAM or ROM)
|
||||
- memory mapped I/O registers
|
||||
- optional use of memory mapped I/O registers
|
||||
- optional use of system ROM routines
|
||||
|
||||
The main target machine is the Commodore-64, which is an example of this.
|
||||
This chapter explains the relevant system details of such a machine.
|
||||
Currently there are two machines that are supported as compiler target (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.
|
||||
|
||||
|
||||
Memory Model
|
||||
@ -113,22 +118,14 @@ CPU
|
||||
Directly Usable Registers
|
||||
-------------------------
|
||||
|
||||
The following 6502 CPU hardware registers are directly usable in program code (and are reserved symbols):
|
||||
The hardware CPU registers are not directly accessible from regular Prog8 code.
|
||||
If you need to mess with them, you'll have to use inline assembly.
|
||||
Be extra wary of the ``X`` register because it is used as an evaluation stack pointer and
|
||||
changing its value you will destroy the evaluation stack and likely crash the program.
|
||||
|
||||
- ``A``, ``X``, ``Y`` the three main cpu registers (8 bits)
|
||||
- the status register (P) carry flag and interrupt disable flag can be written via a couple of special
|
||||
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
|
||||
and read via the ``read_flags()`` function.
|
||||
|
||||
However, you must assume that the 3 hardware registers ``A``, ``X`` and ``Y``
|
||||
are volatile. Their values cannot be depended upon, the compiler will use them as required.
|
||||
Even simple assignments may require modification of one or more of the registers (for instance, when using arrays).
|
||||
|
||||
Even more important, the ``X`` register is used as an evaluation stack pointer.
|
||||
If you mess with it, you will destroy the evaluation stack and likely crash your program.
|
||||
In some cases the compiler will warn you about this, but you should really avoid to use
|
||||
this register. It's possible to store/restore the register's value (using special built in functions)
|
||||
for the cases you really really need to use it directly.
|
||||
The status register (P) carry flag and interrupt disable flag can be written via a couple of special
|
||||
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
|
||||
and read via the ``read_flags()`` function.
|
||||
|
||||
|
||||
Subroutine Calling Conventions
|
||||
@ -155,15 +152,15 @@ You can however install your own IRQ handler.
|
||||
This is possible ofcourse by doing it all using customized inline assembly,
|
||||
but there are a few library routines available to make setting up C-64 IRQs and raster IRQs a lot easier (no assembly code required).
|
||||
|
||||
These routines are::
|
||||
For the C64 these routines are::
|
||||
|
||||
c64utils.set_irqvec()
|
||||
c64utils.set_irqvec_excl()
|
||||
c64.set_irqvec()
|
||||
c64.set_irqvec_excl()
|
||||
|
||||
c64utils.set_rasterirq( <raster line> )
|
||||
c64utils.set_rasterirq_excl( <raster line> )
|
||||
c64.set_rasterirq( <raster line> )
|
||||
c64.set_rasterirq_excl( <raster line> )
|
||||
|
||||
c64utils.restore_irqvec() ; set it back to the systems default irq handler
|
||||
c64.restore_irqvec() ; set it back to the systems default irq handler
|
||||
|
||||
If you activate an IRQ handler with one of these, it expects the handler to be defined
|
||||
as a subroutine ``irq`` in the module ``irq`` so like this::
|
||||
@ -173,3 +170,4 @@ as a subroutine ``irq`` in the module ``irq`` so like this::
|
||||
; ... irq handling here ...
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,26 +2,13 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- aliases for imported symbols for example perhaps '%alias print = c64scr.print'
|
||||
- option to load library files from a directory instead of the embedded ones (easier library development/debugging)
|
||||
|
||||
- investigate support for 8bitguy's Commander X16 platform https://murray2.com/forums/commander-x16.9/
|
||||
|
||||
|
||||
Memory Block Operations integrated in language?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
array/string memory block operations?
|
||||
|
||||
- array operations
|
||||
copy (from another array with the same length), shift-N(left,right), rotate-N(left,right)
|
||||
clear (set whole array to the given value, default 0)
|
||||
|
||||
- array operations ofcourse work identical on vars and on memory mapped vars of these types.
|
||||
|
||||
- strings: identical operations as on array.
|
||||
|
||||
For now, we have the ``memcopy`` and ``memset`` builtin functions.
|
||||
- optimize assignment codegeneration
|
||||
- get rid of all TODO's ;-)
|
||||
- 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' ?
|
||||
- investigate support for 8bitguy's Commander X16 platform https://www.commanderx16.com and https://github.com/commanderx16/x16-docs
|
||||
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
|
||||
|
||||
|
||||
More optimizations
|
||||
@ -29,15 +16,16 @@ More optimizations
|
||||
|
||||
Add more compiler optimizations to the existing ones.
|
||||
|
||||
- more targeted optimizations for assigment asm code, such as the following:
|
||||
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
|
||||
- remove unreachable code after an exit(), return or goto
|
||||
- working subroutine inlining (start with trivial routines, grow to taking care of vars and identifier refs to them)
|
||||
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
|
||||
the program will then rely solely on the values as they are in memory at the time of program startup.
|
||||
- Also some library routines and code patterns could perhaps be optimized further
|
||||
- can the parameter passing to subroutines be optimized to avoid copying?
|
||||
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
|
||||
- more optimizations on the language AST level
|
||||
- more optimizations on the final assembly source level
|
||||
- note: abandoned subroutine inlining because of problems referencing non-local stuff. Can't move everything around.
|
||||
|
||||
|
||||
Eval stack redesign? (lot of work)
|
||||
@ -45,13 +33,13 @@ Eval stack redesign? (lot of work)
|
||||
|
||||
The eval stack is now a split lsb/msb stack using X as the stackpointer.
|
||||
Is it easier/faster to just use a single page unsplit stack?
|
||||
It could then even be moved into the zeropage to greatly reduce code size and slowness.
|
||||
It could then even be moved into the zeropage to reduce code size and slowness.
|
||||
|
||||
Or just move the LSB portion into a slab of the zeropage.
|
||||
|
||||
Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly
|
||||
without having to index with X into the eval stack all the time?
|
||||
This could GREATLY improvde code size and speed for operatios that work on just a single value.
|
||||
This could GREATLY improve code size and speed for operations that work on just a single value.
|
||||
|
||||
|
||||
Bug Fixing
|
||||
@ -64,4 +52,3 @@ Misc
|
||||
|
||||
Several ideas were discussed on my reddit post
|
||||
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%zeropage dontuse
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
@ -20,101 +19,90 @@ main {
|
||||
|
||||
; LEN/STRLEN
|
||||
ubyte length = len(name)
|
||||
if length!=5 c64scr.print("error len1\n")
|
||||
if length!=5 txt.print("error len1\n")
|
||||
length = len(uwarr)
|
||||
if length!=5 c64scr.print("error len2\n")
|
||||
if length!=5 txt.print("error len2\n")
|
||||
length=strlen(name)
|
||||
if length!=5 c64scr.print("error strlen1\n")
|
||||
if length!=5 txt.print("error strlen1\n")
|
||||
name[3] = 0
|
||||
length=strlen(name)
|
||||
if length!=3 c64scr.print("error strlen2\n")
|
||||
if length!=3 txt.print("error strlen2\n")
|
||||
|
||||
; MAX
|
||||
ub = max(ubarr)
|
||||
if ub!=199 c64scr.print("error max1\n")
|
||||
if ub!=199 txt.print("error max1\n")
|
||||
bb = max(barr)
|
||||
if bb!=99 c64scr.print("error max2\n")
|
||||
if bb!=99 txt.print("error max2\n")
|
||||
uw = max(uwarr)
|
||||
if uw!=4444 c64scr.print("error max3\n")
|
||||
if uw!=4444 txt.print("error max3\n")
|
||||
ww = max(warr)
|
||||
if ww!=999 c64scr.print("error max4\n")
|
||||
if ww!=999 txt.print("error max4\n")
|
||||
ff = max(farr)
|
||||
if ff!=999.9 c64scr.print("error max5\n")
|
||||
if ff!=999.9 txt.print("error max5\n")
|
||||
|
||||
; MIN
|
||||
ub = min(ubarr)
|
||||
if ub!=0 c64scr.print("error min1\n")
|
||||
if ub!=0 txt.print("error min1\n")
|
||||
bb = min(barr)
|
||||
if bb!=-122 c64scr.print("error min2\n")
|
||||
if bb!=-122 txt.print("error min2\n")
|
||||
uw = min(uwarr)
|
||||
if uw!=0 c64scr.print("error min3\n")
|
||||
if uw!=0 txt.print("error min3\n")
|
||||
ww = min(warr)
|
||||
if ww!=-4444 c64scr.print("error min4\n")
|
||||
if ww!=-4444 txt.print("error min4\n")
|
||||
ff = min(farr)
|
||||
if ff!=-4444.4 c64scr.print("error min5\n")
|
||||
if ff!=-4444.4 txt.print("error min5\n")
|
||||
|
||||
; SUM
|
||||
uw = sum(ubarr)
|
||||
if uw!=420 c64scr.print("error sum1\n")
|
||||
if uw!=420 txt.print("error sum1\n")
|
||||
ww = sum(barr)
|
||||
if ww!=-101 c64scr.print("error sum2\n")
|
||||
if ww!=-101 txt.print("error sum2\n")
|
||||
uw = sum(uwarr)
|
||||
if uw!=6665 c64scr.print("error sum3\n")
|
||||
if uw!=6665 txt.print("error sum3\n")
|
||||
ww = sum(warr)
|
||||
if ww!=-4223 c64scr.print("error sum4\n")
|
||||
if ww!=-4223 txt.print("error sum4\n")
|
||||
ff = sum(farr)
|
||||
if ff!=-4222.4 c64scr.print("error sum5\n")
|
||||
if ff!=-4222.4 txt.print("error sum5\n")
|
||||
|
||||
; ANY
|
||||
ub = any(ubarr)
|
||||
if ub==0 c64scr.print("error any1\n")
|
||||
if ub==0 txt.print("error any1\n")
|
||||
ub = any(barr)
|
||||
if ub==0 c64scr.print("error any2\n")
|
||||
if ub==0 txt.print("error any2\n")
|
||||
ub = any(uwarr)
|
||||
if ub==0 c64scr.print("error any3\n")
|
||||
if ub==0 txt.print("error any3\n")
|
||||
ub = any(warr)
|
||||
if ub==0 c64scr.print("error any4\n")
|
||||
if ub==0 txt.print("error any4\n")
|
||||
ub = any(farr)
|
||||
if ub==0 c64scr.print("error any5\n")
|
||||
if ub==0 txt.print("error any5\n")
|
||||
|
||||
; ALL
|
||||
ub = all(ubarr)
|
||||
if ub==1 c64scr.print("error all1\n")
|
||||
if ub==1 txt.print("error all1\n")
|
||||
ub = all(barr)
|
||||
if ub==1 c64scr.print("error all2\n")
|
||||
if ub==1 txt.print("error all2\n")
|
||||
ub = all(uwarr)
|
||||
if ub==1 c64scr.print("error all3\n")
|
||||
if ub==1 txt.print("error all3\n")
|
||||
ub = all(warr)
|
||||
if ub==1 c64scr.print("error all4\n")
|
||||
if ub==1 txt.print("error all4\n")
|
||||
ub = all(farr)
|
||||
if ub==1 c64scr.print("error all5\n")
|
||||
if ub==1 txt.print("error all5\n")
|
||||
ubarr[1]=$40
|
||||
barr[1]=$40
|
||||
uwarr[1]=$4000
|
||||
warr[1]=$4000
|
||||
farr[1]=1.1
|
||||
ub = all(ubarr)
|
||||
if ub==0 c64scr.print("error all6\n")
|
||||
if ub==0 txt.print("error all6\n")
|
||||
ub = all(barr)
|
||||
if ub==0 c64scr.print("error all7\n")
|
||||
if ub==0 txt.print("error all7\n")
|
||||
ub = all(uwarr)
|
||||
if ub==0 c64scr.print("error all8\n")
|
||||
if ub==0 txt.print("error all8\n")
|
||||
ub = all(warr)
|
||||
if ub==0 c64scr.print("error all9\n")
|
||||
if ub==0 txt.print("error all9\n")
|
||||
ub = all(farr)
|
||||
if ub==0 c64scr.print("error all10\n")
|
||||
if ub==0 txt.print("error all10\n")
|
||||
|
||||
check_eval_stack()
|
||||
|
||||
c64scr.print("\nyou should see no errors above.")
|
||||
txt.print("\nyou should see no errors printed above (only at first run).")
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,384 +1,385 @@
|
||||
%import c64utils
|
||||
;%import c64flt
|
||||
;%option enable_floats
|
||||
%zeropage dontuse
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
c64scr.print("ubyte shift left\n")
|
||||
ubyte A
|
||||
|
||||
txt.print("ubyte shift left\n")
|
||||
A = shiftlb0()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb1()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb2()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb3()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb4()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb5()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb6()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb7()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb8()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftlb9()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
txt.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
c64scr.print("ubyte shift right\n")
|
||||
txt.print("ubyte shift right\n")
|
||||
A = shiftrb0()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb1()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb2()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb3()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb4()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb5()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb6()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb7()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb8()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
A = shiftrb9()
|
||||
c64scr.print_ubbin(A, true)
|
||||
txt.print_ubbin(A, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
txt.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
|
||||
|
||||
|
||||
c64scr.print("signed byte shift left\n")
|
||||
txt.print("signed byte shift left\n")
|
||||
byte signedb
|
||||
signedb = shiftlsb0()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb1()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb2()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb3()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb4()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb5()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb6()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb7()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb8()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftlsb9()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
txt.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
c64scr.print("signed byte shift right\n")
|
||||
txt.print("signed byte shift right\n")
|
||||
signedb = shiftrsb0()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb1()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb2()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb3()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb4()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb5()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb6()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb7()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb8()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
signedb = shiftrsb9()
|
||||
c64scr.print_ubbin(signedb as ubyte, true)
|
||||
txt.print_ubbin(signedb as ubyte, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
txt.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
|
||||
|
||||
|
||||
c64scr.print("uword shift left\n")
|
||||
txt.print("uword shift left\n")
|
||||
uword uw
|
||||
uw = shiftluw0()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw1()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw2()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw3()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw4()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw5()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw6()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw7()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw8()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw9()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw10()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw11()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw12()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw13()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw14()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw15()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw16()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftluw17()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
txt.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
c64scr.print("uword shift right\n")
|
||||
txt.print("uword shift right\n")
|
||||
uw = shiftruw0()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw1()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw2()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw3()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw4()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw5()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw6()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw7()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw8()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw9()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw10()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw11()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw12()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw13()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw14()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw15()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw16()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
uw = shiftruw17()
|
||||
c64scr.print_uwbin(uw, true)
|
||||
txt.print_uwbin(uw, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
txt.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
c64scr.print("signed word shift left\n")
|
||||
txt.print("signed word shift left\n")
|
||||
word sw
|
||||
sw = shiftlsw0()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw1()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw2()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw3()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw4()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw5()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw6()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw7()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw8()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw9()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw10()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw11()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw12()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw13()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw14()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw15()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw16()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftlsw17()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print("enter to continue:\n")
|
||||
txt.print("enter to continue:\n")
|
||||
void c64.CHRIN()
|
||||
|
||||
c64scr.print("signed word shift right\n")
|
||||
txt.print("signed word shift right\n")
|
||||
sw = shiftrsw0()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw1()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw2()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw3()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw4()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw5()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw6()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw7()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw8()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw9()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw10()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw11()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw12()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw13()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw14()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw15()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw16()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
sw = shiftrsw17()
|
||||
c64scr.print_uwbin(sw as uword, true)
|
||||
txt.print_uwbin(sw as uword, true)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -24,91 +23,81 @@ main {
|
||||
|
||||
div_float(0,1,0)
|
||||
div_float(999.9,111.0,9.008108108108107)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub div_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
ubyte r = a1/a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("ubyte ")
|
||||
c64scr.print_ub(a1)
|
||||
c64scr.print(" / ")
|
||||
c64scr.print_ub(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_ub(r)
|
||||
txt.print("err! ")
|
||||
txt.print("ubyte ")
|
||||
txt.print_ub(a1)
|
||||
txt.print(" / ")
|
||||
txt.print_ub(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_ub(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub div_byte(byte a1, byte a2, byte c) {
|
||||
byte r = a1/a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("byte ")
|
||||
c64scr.print_b(a1)
|
||||
c64scr.print(" / ")
|
||||
c64scr.print_b(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_b(r)
|
||||
txt.print("err! ")
|
||||
txt.print("byte ")
|
||||
txt.print_b(a1)
|
||||
txt.print(" / ")
|
||||
txt.print_b(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_b(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub div_uword(uword a1, uword a2, uword c) {
|
||||
uword r = a1/a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("uword ")
|
||||
c64scr.print_uw(a1)
|
||||
c64scr.print(" / ")
|
||||
c64scr.print_uw(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_uw(r)
|
||||
txt.print("err! ")
|
||||
txt.print("uword ")
|
||||
txt.print_uw(a1)
|
||||
txt.print(" / ")
|
||||
txt.print_uw(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_uw(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub div_word(word a1, word a2, word c) {
|
||||
word r = a1/a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("word ")
|
||||
c64scr.print_w(a1)
|
||||
c64scr.print(" / ")
|
||||
c64scr.print_w(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_w(r)
|
||||
txt.print("err! ")
|
||||
txt.print("word ")
|
||||
txt.print_w(a1)
|
||||
txt.print(" / ")
|
||||
txt.print_w(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_w(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub div_float(float a1, float a2, float c) {
|
||||
float r = a1/a2
|
||||
if abs(r-c)<0.00001
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
txt.print("err! ")
|
||||
|
||||
c64scr.print("float ")
|
||||
txt.print("float ")
|
||||
c64flt.print_f(a1)
|
||||
c64scr.print(" / ")
|
||||
txt.print(" / ")
|
||||
c64flt.print_f(a2)
|
||||
c64scr.print(" = ")
|
||||
txt.print(" = ")
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -32,92 +31,81 @@ main {
|
||||
minus_float(0,0,0)
|
||||
minus_float(2.5,1.5,1.0)
|
||||
minus_float(-1.5,3.5,-5.0)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
ubyte r = a1-a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("ubyte ")
|
||||
c64scr.print_ub(a1)
|
||||
c64scr.print(" - ")
|
||||
c64scr.print_ub(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_ub(r)
|
||||
txt.print("err! ")
|
||||
txt.print("ubyte ")
|
||||
txt.print_ub(a1)
|
||||
txt.print(" - ")
|
||||
txt.print_ub(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_ub(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub minus_byte(byte a1, byte a2, byte c) {
|
||||
byte r = a1-a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("byte ")
|
||||
c64scr.print_b(a1)
|
||||
c64scr.print(" - ")
|
||||
c64scr.print_b(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_b(r)
|
||||
txt.print("err! ")
|
||||
txt.print("byte ")
|
||||
txt.print_b(a1)
|
||||
txt.print(" - ")
|
||||
txt.print_b(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_b(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub minus_uword(uword a1, uword a2, uword c) {
|
||||
uword r = a1-a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("uword ")
|
||||
c64scr.print_uw(a1)
|
||||
c64scr.print(" - ")
|
||||
c64scr.print_uw(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_uw(r)
|
||||
txt.print("err! ")
|
||||
txt.print("uword ")
|
||||
txt.print_uw(a1)
|
||||
txt.print(" - ")
|
||||
txt.print_uw(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_uw(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub minus_word(word a1, word a2, word c) {
|
||||
word r = a1-a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("word ")
|
||||
c64scr.print_w(a1)
|
||||
c64scr.print(" - ")
|
||||
c64scr.print_w(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_w(r)
|
||||
txt.print("err! ")
|
||||
txt.print("word ")
|
||||
txt.print_w(a1)
|
||||
txt.print(" - ")
|
||||
txt.print_w(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_w(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub minus_float(float a1, float a2, float c) {
|
||||
float r = a1-a2
|
||||
if abs(r-c)<0.00001
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
txt.print("err! ")
|
||||
|
||||
c64scr.print("float ")
|
||||
txt.print("float ")
|
||||
c64flt.print_f(a1)
|
||||
c64scr.print(" - ")
|
||||
txt.print(" - ")
|
||||
c64flt.print_f(a2)
|
||||
c64scr.print(" = ")
|
||||
txt.print(" = ")
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -26,91 +25,81 @@ main {
|
||||
mul_float(0,0,0)
|
||||
mul_float(2.5,10,25)
|
||||
mul_float(-1.5,10,-15)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
ubyte r = a1*a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("ubyte ")
|
||||
c64scr.print_ub(a1)
|
||||
c64scr.print(" * ")
|
||||
c64scr.print_ub(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_ub(r)
|
||||
txt.print("err! ")
|
||||
txt.print("ubyte ")
|
||||
txt.print_ub(a1)
|
||||
txt.print(" * ")
|
||||
txt.print_ub(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_ub(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub mul_byte(byte a1, byte a2, byte c) {
|
||||
byte r = a1*a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("byte ")
|
||||
c64scr.print_b(a1)
|
||||
c64scr.print(" * ")
|
||||
c64scr.print_b(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_b(r)
|
||||
txt.print("err! ")
|
||||
txt.print("byte ")
|
||||
txt.print_b(a1)
|
||||
txt.print(" * ")
|
||||
txt.print_b(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_b(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub mul_uword(uword a1, uword a2, uword c) {
|
||||
uword r = a1*a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("uword ")
|
||||
c64scr.print_uw(a1)
|
||||
c64scr.print(" * ")
|
||||
c64scr.print_uw(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_uw(r)
|
||||
txt.print("err! ")
|
||||
txt.print("uword ")
|
||||
txt.print_uw(a1)
|
||||
txt.print(" * ")
|
||||
txt.print_uw(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_uw(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub mul_word(word a1, word a2, word c) {
|
||||
word r = a1*a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("word ")
|
||||
c64scr.print_w(a1)
|
||||
c64scr.print(" * ")
|
||||
c64scr.print_w(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_w(r)
|
||||
txt.print("err! ")
|
||||
txt.print("word ")
|
||||
txt.print_w(a1)
|
||||
txt.print(" * ")
|
||||
txt.print_w(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_w(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub mul_float(float a1, float a2, float c) {
|
||||
float r = a1*a2
|
||||
if abs(r-c)<0.00001
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
txt.print("err! ")
|
||||
|
||||
c64scr.print("float ")
|
||||
txt.print("float ")
|
||||
c64flt.print_f(a1)
|
||||
c64scr.print(" * ")
|
||||
txt.print(" * ")
|
||||
c64flt.print_f(a2)
|
||||
c64scr.print(" = ")
|
||||
txt.print(" = ")
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -30,92 +29,81 @@ main {
|
||||
plus_float(1.5,2.5,4.0)
|
||||
plus_float(-1.5,3.5,2.0)
|
||||
plus_float(-1.1,3.3,2.2)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub plus_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
ubyte r = a1+a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("ubyte ")
|
||||
c64scr.print_ub(a1)
|
||||
c64scr.print(" + ")
|
||||
c64scr.print_ub(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_ub(r)
|
||||
txt.print("err! ")
|
||||
txt.print("ubyte ")
|
||||
txt.print_ub(a1)
|
||||
txt.print(" + ")
|
||||
txt.print_ub(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_ub(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub plus_byte(byte a1, byte a2, byte c) {
|
||||
byte r = a1+a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("byte ")
|
||||
c64scr.print_b(a1)
|
||||
c64scr.print(" + ")
|
||||
c64scr.print_b(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_b(r)
|
||||
txt.print("err! ")
|
||||
txt.print("byte ")
|
||||
txt.print_b(a1)
|
||||
txt.print(" + ")
|
||||
txt.print_b(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_b(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub plus_uword(uword a1, uword a2, uword c) {
|
||||
uword r = a1+a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("uword ")
|
||||
c64scr.print_uw(a1)
|
||||
c64scr.print(" + ")
|
||||
c64scr.print_uw(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_uw(r)
|
||||
txt.print("err! ")
|
||||
txt.print("uword ")
|
||||
txt.print_uw(a1)
|
||||
txt.print(" + ")
|
||||
txt.print_uw(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_uw(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub plus_word(word a1, word a2, word c) {
|
||||
word r = a1+a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("word ")
|
||||
c64scr.print_w(a1)
|
||||
c64scr.print(" + ")
|
||||
c64scr.print_w(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_w(r)
|
||||
txt.print("err! ")
|
||||
txt.print("word ")
|
||||
txt.print_w(a1)
|
||||
txt.print(" + ")
|
||||
txt.print_w(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_w(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub plus_float(float a1, float a2, float c) {
|
||||
float r = a1+a2
|
||||
if abs(r-c)<0.00001
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
txt.print("err! ")
|
||||
|
||||
c64scr.print("float ")
|
||||
txt.print("float ")
|
||||
c64flt.print_f(a1)
|
||||
c64scr.print(" + ")
|
||||
txt.print(" + ")
|
||||
c64flt.print_f(a2)
|
||||
c64scr.print(" = ")
|
||||
txt.print(" = ")
|
||||
c64flt.print_f(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%option enable_floats
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
|
||||
c64scr.plot(0,24)
|
||||
txt.plot(0,24)
|
||||
|
||||
ubyte Y
|
||||
ubyte ub=200
|
||||
byte bb=-100
|
||||
uword uw = 2000
|
||||
@ -20,7 +20,7 @@ main {
|
||||
word[3] warr = -1000
|
||||
float[3] flarr = 999.99
|
||||
|
||||
c64scr.print("++\n")
|
||||
txt.print("++\n")
|
||||
ub++
|
||||
bb++
|
||||
uw++
|
||||
@ -51,7 +51,7 @@ main {
|
||||
check_uw(uwarr[1], 2001)
|
||||
check_w(warr[1], -999)
|
||||
|
||||
c64scr.print("--\n")
|
||||
txt.print("--\n")
|
||||
ub--
|
||||
bb--
|
||||
uw--
|
||||
@ -76,76 +76,65 @@ main {
|
||||
check_b(barr[1], -100)
|
||||
check_uw(uwarr[1], 2000)
|
||||
check_w(warr[1], -1000)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub check_ub(ubyte value, ubyte expected) {
|
||||
if value==expected
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print(" ubyte ")
|
||||
c64scr.print_ub(value)
|
||||
txt.print("err! ")
|
||||
txt.print(" ubyte ")
|
||||
txt.print_ub(value)
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_ub(expected)
|
||||
txt.print_ub(expected)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_b(byte value, byte expected) {
|
||||
if value==expected
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print(" byte ")
|
||||
c64scr.print_b(value)
|
||||
txt.print("err! ")
|
||||
txt.print(" byte ")
|
||||
txt.print_b(value)
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_b(expected)
|
||||
txt.print_b(expected)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_uw(uword value, uword expected) {
|
||||
if value==expected
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print(" uword ")
|
||||
c64scr.print_uw(value)
|
||||
txt.print("err! ")
|
||||
txt.print(" uword ")
|
||||
txt.print_uw(value)
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_uw(expected)
|
||||
txt.print_uw(expected)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_w(word value, word expected) {
|
||||
if value==expected
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print(" word ")
|
||||
c64scr.print_w(value)
|
||||
txt.print("err! ")
|
||||
txt.print(" word ")
|
||||
txt.print_w(value)
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_w(expected)
|
||||
txt.print_w(expected)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_fl(float value, float expected) {
|
||||
if value==expected
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print(" float ")
|
||||
txt.print("err! ")
|
||||
txt.print(" float ")
|
||||
c64flt.print_f(value)
|
||||
c64.CHROUT(',')
|
||||
c64flt.print_f(expected)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -15,45 +13,35 @@ main {
|
||||
remainder_uword(40000,511,142)
|
||||
remainder_uword(40000,500,0)
|
||||
remainder_uword(43211,12,11)
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
sub remainder_ubyte(ubyte a1, ubyte a2, ubyte c) {
|
||||
ubyte r = a1%a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("ubyte ")
|
||||
c64scr.print_ub(a1)
|
||||
c64scr.print(" % ")
|
||||
c64scr.print_ub(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_ub(r)
|
||||
txt.print("err! ")
|
||||
txt.print("ubyte ")
|
||||
txt.print_ub(a1)
|
||||
txt.print(" % ")
|
||||
txt.print_ub(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_ub(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub remainder_uword(uword a1, uword a2, uword c) {
|
||||
uword r = a1%a2
|
||||
if r==c
|
||||
c64scr.print(" ok ")
|
||||
txt.print(" ok ")
|
||||
else
|
||||
c64scr.print("err! ")
|
||||
c64scr.print("uword ")
|
||||
c64scr.print_uw(a1)
|
||||
c64scr.print(" % ")
|
||||
c64scr.print_uw(a2)
|
||||
c64scr.print(" = ")
|
||||
c64scr.print_uw(r)
|
||||
txt.print("err! ")
|
||||
txt.print("uword ")
|
||||
txt.print_uw(a1)
|
||||
txt.print(" % ")
|
||||
txt.print_uw(a2)
|
||||
txt.print(" = ")
|
||||
txt.print_uw(r)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import c64flt
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -18,113 +19,113 @@ main {
|
||||
b1 = 10
|
||||
b2 = 10
|
||||
if sgn(b2-b1) != 0
|
||||
c64scr.print("sgn1 error1\n")
|
||||
txt.print("sgn1 error1\n")
|
||||
|
||||
b1 = -100
|
||||
b2 = -100
|
||||
if sgn(b2-b1) != 0
|
||||
c64scr.print("sgn1 error2\n")
|
||||
txt.print("sgn1 error2\n")
|
||||
|
||||
ub1 = 200
|
||||
ub2 = 200
|
||||
if sgn(ub2-ub1) != 0
|
||||
c64scr.print("sgn1 error3\n")
|
||||
txt.print("sgn1 error3\n")
|
||||
|
||||
w1 = 100
|
||||
w2 = 100
|
||||
if sgn(w2-w1) != 0
|
||||
c64scr.print("sgn1 error4\n")
|
||||
txt.print("sgn1 error4\n")
|
||||
|
||||
w1 = -2000
|
||||
w2 = -2000
|
||||
if sgn(w2-w1) != 0
|
||||
c64scr.print("sgn1 error5\n")
|
||||
txt.print("sgn1 error5\n")
|
||||
|
||||
uw1 = 999
|
||||
uw2 = 999
|
||||
if sgn(uw2-uw1) != 0
|
||||
c64scr.print("sgn1 error6\n")
|
||||
txt.print("sgn1 error6\n")
|
||||
|
||||
f1 = 3.45
|
||||
f2 = 3.45
|
||||
if sgn(f2-f1) != 0
|
||||
c64scr.print("sgn1 error7\n")
|
||||
txt.print("sgn1 error7\n")
|
||||
|
||||
|
||||
; -1
|
||||
b1 = 11
|
||||
b2 = 10
|
||||
if sgn(b2-b1) != -1
|
||||
c64scr.print("sgn2 error1\n")
|
||||
txt.print("sgn2 error1\n")
|
||||
|
||||
b1 = -10
|
||||
b2 = -100
|
||||
if sgn(b2-b1) != -1
|
||||
c64scr.print("sgn2 error2\n")
|
||||
txt.print("sgn2 error2\n")
|
||||
|
||||
ub1 = 202
|
||||
ub2 = 200
|
||||
if sgn(ub2 as byte - ub1 as byte) != -1
|
||||
c64scr.print("sgn2 error3\n")
|
||||
txt.print("sgn2 error3\n")
|
||||
|
||||
w1 = 101
|
||||
w2 = 100
|
||||
if sgn(w2-w1) != -1
|
||||
c64scr.print("sgn2 error4\n")
|
||||
txt.print("sgn2 error4\n")
|
||||
|
||||
w1 = -200
|
||||
w2 = -2000
|
||||
if sgn(w2-w1) != -1
|
||||
c64scr.print("sgn2 error5\n")
|
||||
txt.print("sgn2 error5\n")
|
||||
|
||||
uw1 = 2222
|
||||
uw2 = 999
|
||||
if sgn((uw2 as word) - (uw1 as word)) != -1
|
||||
c64scr.print("sgn2 error6a\n")
|
||||
txt.print("sgn2 error6a\n")
|
||||
if sgn(uw2 - uw1) != 1 ; always 0 or 1 if unsigned
|
||||
c64scr.print("sgn2 error6b\n")
|
||||
txt.print("sgn2 error6b\n")
|
||||
|
||||
f1 = 3.45
|
||||
f2 = 1.11
|
||||
if sgn(f2-f1) != -1
|
||||
c64scr.print("sgn2 error7\n")
|
||||
txt.print("sgn2 error7\n")
|
||||
|
||||
; +1
|
||||
b1 = 11
|
||||
b2 = 20
|
||||
if sgn(b2-b1) != 1
|
||||
c64scr.print("sgn3 error1\n")
|
||||
txt.print("sgn3 error1\n")
|
||||
|
||||
b1 = -10
|
||||
b2 = -1
|
||||
if sgn(b2-b1) != 1
|
||||
c64scr.print("sgn3 error2\n")
|
||||
txt.print("sgn3 error2\n")
|
||||
|
||||
ub1 = 202
|
||||
ub2 = 205
|
||||
if sgn(ub2-ub1) != 1
|
||||
c64scr.print("sgn3 error3\n")
|
||||
txt.print("sgn3 error3\n")
|
||||
|
||||
w1 = 101
|
||||
w2 = 200
|
||||
if sgn(w2-w1) != 1
|
||||
c64scr.print("sgn3 error4\n")
|
||||
txt.print("sgn3 error4\n")
|
||||
|
||||
w1 = -200
|
||||
w2 = -20
|
||||
if sgn(w2-w1) != 1
|
||||
c64scr.print("sgn3 error5\n")
|
||||
txt.print("sgn3 error5\n")
|
||||
|
||||
uw1 = 2222
|
||||
uw2 = 9999
|
||||
if sgn(uw2-uw1) != 1
|
||||
c64scr.print("sgn3 error6\n")
|
||||
txt.print("sgn3 error6\n")
|
||||
|
||||
f1 = 3.45
|
||||
f2 = 5.11
|
||||
if sgn(f2-f1) != 1
|
||||
c64scr.print("sgn3 error7\n")
|
||||
txt.print("sgn3 error7\n")
|
||||
|
||||
c64scr.print("should see no sgn errors\n")
|
||||
txt.print("should see no sgn errors\n")
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -13,15 +13,15 @@ main {
|
||||
c64.SPXY[0] = 80
|
||||
c64.SPXY[1] = 100
|
||||
|
||||
c64.SCROLX = c64.SCROLX & %11110111 ; 38 column mode
|
||||
c64.SCROLX &= %11110111 ; 38 column mode
|
||||
|
||||
c64utils.set_rasterirq(1) ; enable animation
|
||||
c64.set_rasterirq(1) ; enable animation
|
||||
|
||||
ubyte target_height = 10
|
||||
ubyte active_height = 24
|
||||
ubyte upwards = true
|
||||
|
||||
forever {
|
||||
repeat {
|
||||
ubyte mountain = 223 ; slope upwards
|
||||
if active_height < target_height {
|
||||
active_height++
|
||||
@ -43,7 +43,7 @@ main {
|
||||
}
|
||||
|
||||
perform_scroll = false
|
||||
c64scr.scroll_left_full(true)
|
||||
txt.scroll_left_full(true)
|
||||
if c64.RASTER & 1
|
||||
c64.SPXY[1] ++
|
||||
else
|
||||
@ -51,17 +51,17 @@ main {
|
||||
|
||||
ubyte yy
|
||||
for yy in 0 to active_height-1 {
|
||||
c64scr.setcc(39, yy, 32, 2) ; clear top of screen
|
||||
txt.setcc(39, yy, 32, 2) ; clear top of screen
|
||||
}
|
||||
c64scr.setcc(39, active_height, mountain, 8) ; mountain edge
|
||||
txt.setcc(39, active_height, mountain, 8) ; mountain edge
|
||||
for yy in active_height+1 to 24 {
|
||||
c64scr.setcc(39, yy, 160, 8) ; draw mountain
|
||||
txt.setcc(39, yy, 160, 8) ; draw mountain
|
||||
}
|
||||
|
||||
yy = rnd()
|
||||
if yy > 100 {
|
||||
; draw a star
|
||||
c64scr.setcc(39, yy % (active_height-1), '.', rnd())
|
||||
txt.setcc(39, yy % (active_height-1), '.', rnd())
|
||||
}
|
||||
|
||||
if yy > 200 {
|
||||
@ -74,12 +74,12 @@ main {
|
||||
tree = 65
|
||||
if rnd() > 130
|
||||
treecolor = 13
|
||||
c64scr.setcc(39, active_height, tree, treecolor)
|
||||
txt.setcc(39, active_height, tree, treecolor)
|
||||
}
|
||||
|
||||
if yy > 235 {
|
||||
; draw a camel
|
||||
c64scr.setcc(39, active_height, 94, 9)
|
||||
txt.setcc(39, active_height, 94, 9)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
%zeropage basicsafe
|
||||
%import c64lib
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
c64scr.print("playing the music from boulderdash,\nmade in 1984 by peter liepa.\n\n")
|
||||
c64utils.set_rasterirq(60) ; enable raster irq
|
||||
txt.print("playing the music from boulderdash,\nmade in 1984 by peter liepa.\n\n")
|
||||
c64.set_rasterirq(60) ; enable raster irq
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64lib
|
||||
%import c64textio
|
||||
|
||||
main {
|
||||
|
||||
@ -12,11 +12,11 @@ sub start() {
|
||||
c64.SR2 = %00000000
|
||||
c64.MVOL = 15
|
||||
|
||||
c64scr.print("will play the music from boulderdash,\nmade in 1984 by peter liepa.\npress enter to start: ")
|
||||
txt.print("will play the music from boulderdash,\nmade in 1984 by peter liepa.\npress enter to start: ")
|
||||
void c64.CHRIN()
|
||||
c64.CLEARSCR()
|
||||
|
||||
forever {
|
||||
repeat {
|
||||
uword note
|
||||
for note in notes {
|
||||
ubyte note1 = lsb(note)
|
||||
@ -37,21 +37,20 @@ sub start() {
|
||||
}
|
||||
|
||||
sub delay() {
|
||||
ubyte d
|
||||
for d in 0 to 12 {
|
||||
while c64.RASTER!=0 {
|
||||
; tempo delay synced to screen refresh
|
||||
repeat 8 {
|
||||
ubyte jiffy = c64.TIME_LO
|
||||
while c64.TIME_LO==jiffy {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub print_notes(ubyte n1, ubyte n2) {
|
||||
c64.CHROUT('\n')
|
||||
c64scr.plot(n1/2, 24)
|
||||
c64.COLOR=7
|
||||
txt.plot(n1/2, 24)
|
||||
txt.color(7)
|
||||
c64.CHROUT('Q')
|
||||
c64scr.plot(n2/2, 24)
|
||||
c64.COLOR=4
|
||||
txt.plot(n2/2, 24)
|
||||
txt.color(4)
|
||||
c64.CHROUT('Q')
|
||||
}
|
||||
|
||||
|
@ -1,250 +0,0 @@
|
||||
%import c64lib
|
||||
|
||||
; bitmap pixel graphics module for the C64
|
||||
; only black/white monchrome for now
|
||||
|
||||
; you could put this code at $4000 which is after the bitmap screen in memory ($2000-$3fff),
|
||||
; this leaves more space for user program code.
|
||||
|
||||
graphics {
|
||||
const uword bitmap_address = $2000
|
||||
|
||||
sub enable_bitmap_mode() {
|
||||
; enable bitmap screen, erase it and set colors to black/white.
|
||||
c64.SCROLY |= %00100000
|
||||
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
|
||||
memset(bitmap_address, 320*200/8, 0)
|
||||
c64scr.clear_screen($10, 0) ; pixel color $1 (white) backround $0 (black)
|
||||
}
|
||||
|
||||
|
||||
sub line(uword x1, ubyte y1, uword x2, ubyte y2) {
|
||||
; Bresenham algorithm
|
||||
word dx
|
||||
word dy
|
||||
byte ix = 1
|
||||
byte iy = 1
|
||||
if x2>x1 {
|
||||
dx = x2-x1
|
||||
} else {
|
||||
ix = -1
|
||||
dx = x1-x2
|
||||
}
|
||||
if y2>y1 {
|
||||
dy = y2-y1
|
||||
} else {
|
||||
iy = -1
|
||||
dy = y1-y2
|
||||
}
|
||||
word dx2 = 2 * dx
|
||||
word dy2 = 2 * dy
|
||||
word d = 0
|
||||
plotx = x1
|
||||
|
||||
if dx >= dy {
|
||||
if ix<0 {
|
||||
forever {
|
||||
graphics.plot(y1)
|
||||
if plotx==x2
|
||||
return
|
||||
plotx--
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1 += iy
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forever {
|
||||
graphics.plot(y1)
|
||||
if plotx==x2
|
||||
return
|
||||
plotx++
|
||||
d += dy2
|
||||
if d > dx {
|
||||
y1 += iy
|
||||
d -= dx2
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if iy<0 {
|
||||
forever {
|
||||
plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
y1--
|
||||
d += dx2
|
||||
if d > dy {
|
||||
plotx += ix as word
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
} else {
|
||||
forever {
|
||||
plot(y1)
|
||||
if y1 == y2
|
||||
return
|
||||
y1++
|
||||
d += dx2
|
||||
if d > dy {
|
||||
plotx += ix as word
|
||||
d -= dy2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
|
||||
; Midpoint algorithm
|
||||
ubyte ploty
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx
|
||||
|
||||
while xx>=yy {
|
||||
plotx = xcenter + xx
|
||||
ploty = ycenter + yy
|
||||
plot(ploty)
|
||||
plotx = xcenter - xx
|
||||
plot(ploty)
|
||||
plotx = xcenter + xx
|
||||
ploty = ycenter - yy
|
||||
plot(ploty)
|
||||
plotx = xcenter - xx
|
||||
plot(ploty)
|
||||
plotx = xcenter + yy
|
||||
ploty = ycenter + xx
|
||||
plot(ploty)
|
||||
plotx = xcenter - yy
|
||||
plot(ploty)
|
||||
plotx = xcenter + yy
|
||||
ploty = ycenter - xx
|
||||
plot(ploty)
|
||||
plotx = xcenter - yy
|
||||
plot(ploty)
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub disc(uword cx, ubyte cy, ubyte radius) {
|
||||
; Midpoint algorithm, filled
|
||||
ubyte xx = radius
|
||||
ubyte yy = 0
|
||||
byte decisionOver2 = 1-xx
|
||||
|
||||
while xx>=yy {
|
||||
for plotx in cx to cx+xx {
|
||||
plot(cy + yy)
|
||||
plot(cy - yy)
|
||||
}
|
||||
for plotx in cx-xx to cx-1 {
|
||||
plot(cy + yy)
|
||||
plot(cy - yy)
|
||||
}
|
||||
for plotx in cx to cx+yy {
|
||||
plot(cy + xx)
|
||||
plot(cy - xx)
|
||||
}
|
||||
for plotx in cx-yy to cx {
|
||||
plot(cy + xx)
|
||||
plot(cy - xx)
|
||||
}
|
||||
yy++
|
||||
if decisionOver2<=0
|
||||
decisionOver2 += 2*yy+1
|
||||
else {
|
||||
xx--
|
||||
decisionOver2 += 2*(yy-xx)+1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; here is the non-asm code for the plot routine below:
|
||||
; sub plot_nonasm(uword px, ubyte py) {
|
||||
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
|
||||
; uword addr = bitmap_address + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
|
||||
; @(addr) |= ormask[lsb(px) & 7]
|
||||
; }
|
||||
|
||||
uword plotx ; 0..319
|
||||
|
||||
asmsub plot(ubyte ploty @A) { ; plotx is 16 bits 0 to 319... doesn't fit in a register
|
||||
%asm {{
|
||||
tay
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda plotx+1
|
||||
sta c64.SCRATCH_ZPWORD2+1
|
||||
lsr a ; 0
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
lda plotx
|
||||
pha
|
||||
and #7
|
||||
tax
|
||||
|
||||
lda _y_lookup_lo,y
|
||||
clc
|
||||
adc c64.SCRATCH_ZPWORD2
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
lda _y_lookup_hi,y
|
||||
adc c64.SCRATCH_ZPWORD2+1
|
||||
sta c64.SCRATCH_ZPWORD2+1
|
||||
|
||||
pla ; plotx
|
||||
and #%11111000
|
||||
tay
|
||||
lda (c64.SCRATCH_ZPWORD2),y
|
||||
ora _ormask,x
|
||||
sta (c64.SCRATCH_ZPWORD2),y
|
||||
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
|
||||
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
|
||||
|
||||
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
|
||||
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
|
||||
; the y lookup tables encode this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
|
||||
_y_lookup_hi
|
||||
.byte $20, $20, $20, $20, $20, $20, $20, $20, $21, $21, $21, $21, $21, $21, $21, $21
|
||||
.byte $22, $22, $22, $22, $22, $22, $22, $22, $23, $23, $23, $23, $23, $23, $23, $23
|
||||
.byte $25, $25, $25, $25, $25, $25, $25, $25, $26, $26, $26, $26, $26, $26, $26, $26
|
||||
.byte $27, $27, $27, $27, $27, $27, $27, $27, $28, $28, $28, $28, $28, $28, $28, $28
|
||||
.byte $2a, $2a, $2a, $2a, $2a, $2a, $2a, $2a, $2b, $2b, $2b, $2b, $2b, $2b, $2b, $2b
|
||||
.byte $2c, $2c, $2c, $2c, $2c, $2c, $2c, $2c, $2d, $2d, $2d, $2d, $2d, $2d, $2d, $2d
|
||||
.byte $2f, $2f, $2f, $2f, $2f, $2f, $2f, $2f, $30, $30, $30, $30, $30, $30, $30, $30
|
||||
.byte $31, $31, $31, $31, $31, $31, $31, $31, $32, $32, $32, $32, $32, $32, $32, $32
|
||||
.byte $34, $34, $34, $34, $34, $34, $34, $34, $35, $35, $35, $35, $35, $35, $35, $35
|
||||
.byte $36, $36, $36, $36, $36, $36, $36, $36, $37, $37, $37, $37, $37, $37, $37, $37
|
||||
.byte $39, $39, $39, $39, $39, $39, $39, $39, $3a, $3a, $3a, $3a, $3a, $3a, $3a, $3a
|
||||
.byte $3b, $3b, $3b, $3b, $3b, $3b, $3b, $3b, $3c, $3c, $3c, $3c, $3c, $3c, $3c, $3c
|
||||
.byte $3e, $3e, $3e, $3e, $3e, $3e, $3e, $3e
|
||||
|
||||
_y_lookup_lo
|
||||
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
|
||||
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
|
||||
.byte $00, $01, $02, $03, $04, $05, $06, $07
|
||||
}}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
@ -12,109 +12,98 @@ main {
|
||||
v1 = 100
|
||||
v2 = 127
|
||||
if v1==v2
|
||||
c64scr.print("error in 100==127!\n")
|
||||
txt.print("error in 100==127!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 not == 127\n")
|
||||
txt.print("ok: 100 not == 127\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("ok: 100 != 127\n")
|
||||
txt.print("ok: 100 != 127\n")
|
||||
else
|
||||
c64scr.print("error in 100!=127!\n")
|
||||
txt.print("error in 100!=127!\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("ok: 100 < 127\n")
|
||||
txt.print("ok: 100 < 127\n")
|
||||
else
|
||||
c64scr.print("error in 100<127!\n")
|
||||
txt.print("error in 100<127!\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: 100 <= 127\n")
|
||||
txt.print("ok: 100 <= 127\n")
|
||||
else
|
||||
c64scr.print("error in 100<=127!\n")
|
||||
txt.print("error in 100<=127!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in 100>127!\n")
|
||||
txt.print("error in 100>127!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 is not >127\n")
|
||||
txt.print("ok: 100 is not >127\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("error in 100>=127!\n")
|
||||
txt.print("error in 100>=127!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 is not >=127\n")
|
||||
txt.print("ok: 100 is not >=127\n")
|
||||
|
||||
|
||||
v1 = 125
|
||||
v2 = 22
|
||||
if v1==v2
|
||||
c64scr.print("error in 125==22!\n")
|
||||
txt.print("error in 125==22!\n")
|
||||
else
|
||||
c64scr.print("ok: 125 not == 22\n")
|
||||
txt.print("ok: 125 not == 22\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("ok: 125 != 22\n")
|
||||
txt.print("ok: 125 != 22\n")
|
||||
else
|
||||
c64scr.print("error in 125!=22!\n")
|
||||
txt.print("error in 125!=22!\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in 125<22!\n")
|
||||
txt.print("error in 125<22!\n")
|
||||
else
|
||||
c64scr.print("ok: 125 is not < 22\n")
|
||||
txt.print("ok: 125 is not < 22\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("error in 125<=22!\n")
|
||||
txt.print("error in 125<=22!\n")
|
||||
else
|
||||
c64scr.print("ok: 125 is not <= 22\n")
|
||||
txt.print("ok: 125 is not <= 22\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("ok: 125 > 22\n")
|
||||
txt.print("ok: 125 > 22\n")
|
||||
else
|
||||
c64scr.print("error in 125>22!\n")
|
||||
txt.print("error in 125>22!\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: 125 >= 22\n")
|
||||
txt.print("ok: 125 >= 22\n")
|
||||
else
|
||||
c64scr.print("error in 125>=22!\n")
|
||||
txt.print("error in 125>=22!\n")
|
||||
|
||||
v1 = 22
|
||||
v2 = 22
|
||||
if v1==v2
|
||||
c64scr.print("ok: 22 == 22\n")
|
||||
txt.print("ok: 22 == 22\n")
|
||||
else
|
||||
c64scr.print("error in 22==22!\n")
|
||||
txt.print("error in 22==22!\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("error in 22!=22!\n")
|
||||
txt.print("error in 22!=22!\n")
|
||||
else
|
||||
c64scr.print("ok: 22 is not != 22\n")
|
||||
txt.print("ok: 22 is not != 22\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in 22<22!\n")
|
||||
txt.print("error in 22<22!\n")
|
||||
else
|
||||
c64scr.print("ok: 22 is not < 22\n")
|
||||
txt.print("ok: 22 is not < 22\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: 22 <= 22\n")
|
||||
txt.print("ok: 22 <= 22\n")
|
||||
else
|
||||
c64scr.print("error in 22<=22!\n")
|
||||
txt.print("error in 22<=22!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in 22>22!\n")
|
||||
txt.print("error in 22>22!\n")
|
||||
else
|
||||
c64scr.print("ok: 22 is not > 22\n")
|
||||
txt.print("ok: 22 is not > 22\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: 22 >= 22\n")
|
||||
txt.print("ok: 22 >= 22\n")
|
||||
else
|
||||
c64scr.print("error in 22>=22!\n")
|
||||
|
||||
check_eval_stack()
|
||||
}
|
||||
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
txt.print("error in 22>=22!\n")
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
@ -12,109 +12,98 @@ main {
|
||||
v1 = 1.11
|
||||
v2 = 699.99
|
||||
if v1==v2
|
||||
c64scr.print("error in 1.11==699.99!\n")
|
||||
txt.print("error in 1.11==699.99!\n")
|
||||
else
|
||||
c64scr.print("ok: 1.11 not == 699.99\n")
|
||||
txt.print("ok: 1.11 not == 699.99\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("ok: 1.11 != 699.99\n")
|
||||
txt.print("ok: 1.11 != 699.99\n")
|
||||
else
|
||||
c64scr.print("error in 1.11!=699.99!\n")
|
||||
txt.print("error in 1.11!=699.99!\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("ok: 1.11 < 699.99\n")
|
||||
txt.print("ok: 1.11 < 699.99\n")
|
||||
else
|
||||
c64scr.print("error in 1.11<699.99!\n")
|
||||
txt.print("error in 1.11<699.99!\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: 1.11 <= 699.99\n")
|
||||
txt.print("ok: 1.11 <= 699.99\n")
|
||||
else
|
||||
c64scr.print("error in 1.11<=699.99!\n")
|
||||
txt.print("error in 1.11<=699.99!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in 1.11>699.99!\n")
|
||||
txt.print("error in 1.11>699.99!\n")
|
||||
else
|
||||
c64scr.print("ok: 1.11 is not >699.99\n")
|
||||
txt.print("ok: 1.11 is not >699.99\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("error in 1.11>=699.99!\n")
|
||||
txt.print("error in 1.11>=699.99!\n")
|
||||
else
|
||||
c64scr.print("ok: 1.11 is not >=699.99\n")
|
||||
txt.print("ok: 1.11 is not >=699.99\n")
|
||||
|
||||
|
||||
v1 = 555.5
|
||||
v2 = -22.2
|
||||
if v1==v2
|
||||
c64scr.print("error in 555.5==-22.2!\n")
|
||||
txt.print("error in 555.5==-22.2!\n")
|
||||
else
|
||||
c64scr.print("ok: 555.5 not == -22.2\n")
|
||||
txt.print("ok: 555.5 not == -22.2\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("ok: 555.5 != -22.2\n")
|
||||
txt.print("ok: 555.5 != -22.2\n")
|
||||
else
|
||||
c64scr.print("error in 555.5!=-22.2!\n")
|
||||
txt.print("error in 555.5!=-22.2!\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in 555.5<-22.2!\n")
|
||||
txt.print("error in 555.5<-22.2!\n")
|
||||
else
|
||||
c64scr.print("ok: 555.5 is not < -22.2\n")
|
||||
txt.print("ok: 555.5 is not < -22.2\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("error in 555.5<=-22.2!\n")
|
||||
txt.print("error in 555.5<=-22.2!\n")
|
||||
else
|
||||
c64scr.print("ok: 555.5 is not <= -22.2\n")
|
||||
txt.print("ok: 555.5 is not <= -22.2\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("ok: 555.5 > -22.2\n")
|
||||
txt.print("ok: 555.5 > -22.2\n")
|
||||
else
|
||||
c64scr.print("error in 555.5>-22.2!\n")
|
||||
txt.print("error in 555.5>-22.2!\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: 555.5 >= -22.2\n")
|
||||
txt.print("ok: 555.5 >= -22.2\n")
|
||||
else
|
||||
c64scr.print("error in 555.5>=-22.2!\n")
|
||||
txt.print("error in 555.5>=-22.2!\n")
|
||||
|
||||
v1 = -22.2
|
||||
v2 = -22.2
|
||||
if v1==v2
|
||||
c64scr.print("ok: -22.2 == -22.2\n")
|
||||
txt.print("ok: -22.2 == -22.2\n")
|
||||
else
|
||||
c64scr.print("error in -22.2==-22.2!\n")
|
||||
txt.print("error in -22.2==-22.2!\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("error in -22.2!=-22.2!\n")
|
||||
txt.print("error in -22.2!=-22.2!\n")
|
||||
else
|
||||
c64scr.print("ok: -22.2 is not != -22.2\n")
|
||||
txt.print("ok: -22.2 is not != -22.2\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in -22.2<-22.2!\n")
|
||||
txt.print("error in -22.2<-22.2!\n")
|
||||
else
|
||||
c64scr.print("ok: -22.2 is not < -22.2\n")
|
||||
txt.print("ok: -22.2 is not < -22.2\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: -22.2 <= -22.2\n")
|
||||
txt.print("ok: -22.2 <= -22.2\n")
|
||||
else
|
||||
c64scr.print("error in -22.2<=-22.2!\n")
|
||||
txt.print("error in -22.2<=-22.2!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in -22.2>-22.2!\n")
|
||||
txt.print("error in -22.2>-22.2!\n")
|
||||
else
|
||||
c64scr.print("ok: -22.2 is not > -22.2\n")
|
||||
txt.print("ok: -22.2 is not > -22.2\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: -22.2 >= -22.2\n")
|
||||
txt.print("ok: -22.2 >= -22.2\n")
|
||||
else
|
||||
c64scr.print("error in -22.2>=-22.2!\n")
|
||||
|
||||
check_eval_stack()
|
||||
txt.print("error in -22.2>=-22.2!\n")
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
@ -12,109 +12,98 @@ main {
|
||||
v1 = 100
|
||||
v2 = 200
|
||||
if v1==v2
|
||||
c64scr.print("error in 100==200!\n")
|
||||
txt.print("error in 100==200!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 not == 200\n")
|
||||
txt.print("ok: 100 not == 200\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("ok: 100 != 200\n")
|
||||
txt.print("ok: 100 != 200\n")
|
||||
else
|
||||
c64scr.print("error in 100!=200!\n")
|
||||
txt.print("error in 100!=200!\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("ok: 100 < 200\n")
|
||||
txt.print("ok: 100 < 200\n")
|
||||
else
|
||||
c64scr.print("error in 100<200!\n")
|
||||
txt.print("error in 100<200!\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: 100 <= 200\n")
|
||||
txt.print("ok: 100 <= 200\n")
|
||||
else
|
||||
c64scr.print("error in 100<=200!\n")
|
||||
txt.print("error in 100<=200!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in 100>200!\n")
|
||||
txt.print("error in 100>200!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 is not >200\n")
|
||||
txt.print("ok: 100 is not >200\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("error in 100>=200!\n")
|
||||
txt.print("error in 100>=200!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 is not >=200\n")
|
||||
txt.print("ok: 100 is not >=200\n")
|
||||
|
||||
|
||||
v1 = 155
|
||||
v2 = 22
|
||||
if v1==v2
|
||||
c64scr.print("error in 155==22!\n")
|
||||
txt.print("error in 155==22!\n")
|
||||
else
|
||||
c64scr.print("ok: 155 not == 22\n")
|
||||
txt.print("ok: 155 not == 22\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("ok: 155 != 22\n")
|
||||
txt.print("ok: 155 != 22\n")
|
||||
else
|
||||
c64scr.print("error in 155!=22!\n")
|
||||
txt.print("error in 155!=22!\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in 155<22!\n")
|
||||
txt.print("error in 155<22!\n")
|
||||
else
|
||||
c64scr.print("ok: 155 is not < 22\n")
|
||||
txt.print("ok: 155 is not < 22\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("error in 155<=22!\n")
|
||||
txt.print("error in 155<=22!\n")
|
||||
else
|
||||
c64scr.print("ok: 155 is not <= 22\n")
|
||||
txt.print("ok: 155 is not <= 22\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("ok: 155 > 22\n")
|
||||
txt.print("ok: 155 > 22\n")
|
||||
else
|
||||
c64scr.print("error in 155>22!\n")
|
||||
txt.print("error in 155>22!\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: 155 >= 22\n")
|
||||
txt.print("ok: 155 >= 22\n")
|
||||
else
|
||||
c64scr.print("error in 155>=22!\n")
|
||||
txt.print("error in 155>=22!\n")
|
||||
|
||||
v1 = 22
|
||||
v2 = 22
|
||||
if v1==v2
|
||||
c64scr.print("ok: 22 == 22\n")
|
||||
txt.print("ok: 22 == 22\n")
|
||||
else
|
||||
c64scr.print("error in 22==22!\n")
|
||||
txt.print("error in 22==22!\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("error in 22!=22!\n")
|
||||
txt.print("error in 22!=22!\n")
|
||||
else
|
||||
c64scr.print("ok: 22 is not != 22\n")
|
||||
txt.print("ok: 22 is not != 22\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in 22<22!\n")
|
||||
txt.print("error in 22<22!\n")
|
||||
else
|
||||
c64scr.print("ok: 22 is not < 22\n")
|
||||
txt.print("ok: 22 is not < 22\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: 22 <= 22\n")
|
||||
txt.print("ok: 22 <= 22\n")
|
||||
else
|
||||
c64scr.print("error in 22<=22!\n")
|
||||
txt.print("error in 22<=22!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in 22>22!\n")
|
||||
txt.print("error in 22>22!\n")
|
||||
else
|
||||
c64scr.print("ok: 22 is not > 22\n")
|
||||
txt.print("ok: 22 is not > 22\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: 22 >= 22\n")
|
||||
txt.print("ok: 22 >= 22\n")
|
||||
else
|
||||
c64scr.print("error in 22>=22!\n")
|
||||
|
||||
check_eval_stack()
|
||||
txt.print("error in 22>=22!\n")
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
@ -12,109 +12,98 @@ main {
|
||||
v1 = 100
|
||||
v2 = 64444
|
||||
if v1==v2
|
||||
c64scr.print("error in 100==64444!\n")
|
||||
txt.print("error in 100==64444!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 not == 64444\n")
|
||||
txt.print("ok: 100 not == 64444\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("ok: 100 != 64444\n")
|
||||
txt.print("ok: 100 != 64444\n")
|
||||
else
|
||||
c64scr.print("error in 100!=64444!\n")
|
||||
txt.print("error in 100!=64444!\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("ok: 100 < 64444\n")
|
||||
txt.print("ok: 100 < 64444\n")
|
||||
else
|
||||
c64scr.print("error in 100<64444!\n")
|
||||
txt.print("error in 100<64444!\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: 100 <= 64444\n")
|
||||
txt.print("ok: 100 <= 64444\n")
|
||||
else
|
||||
c64scr.print("error in 100<=64444!\n")
|
||||
txt.print("error in 100<=64444!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in 100>64444!\n")
|
||||
txt.print("error in 100>64444!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 is not >64444\n")
|
||||
txt.print("ok: 100 is not >64444\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("error in 100>=64444!\n")
|
||||
txt.print("error in 100>=64444!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 is not >=64444\n")
|
||||
txt.print("ok: 100 is not >=64444\n")
|
||||
|
||||
|
||||
v1 = 5555
|
||||
v2 = 322
|
||||
if v1==v2
|
||||
c64scr.print("error in 5555==322!\n")
|
||||
txt.print("error in 5555==322!\n")
|
||||
else
|
||||
c64scr.print("ok: 5555 not == 322\n")
|
||||
txt.print("ok: 5555 not == 322\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("ok: 5555 != 322\n")
|
||||
txt.print("ok: 5555 != 322\n")
|
||||
else
|
||||
c64scr.print("error in 5555!=322!\n")
|
||||
txt.print("error in 5555!=322!\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in 5555<322!\n")
|
||||
txt.print("error in 5555<322!\n")
|
||||
else
|
||||
c64scr.print("ok: 5555 is not < 322\n")
|
||||
txt.print("ok: 5555 is not < 322\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("error in 5555<=322!\n")
|
||||
txt.print("error in 5555<=322!\n")
|
||||
else
|
||||
c64scr.print("ok: 5555 is not <= 322\n")
|
||||
txt.print("ok: 5555 is not <= 322\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("ok: 5555 > 322\n")
|
||||
txt.print("ok: 5555 > 322\n")
|
||||
else
|
||||
c64scr.print("error in 5555>322!\n")
|
||||
txt.print("error in 5555>322!\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: 5555 >= 322\n")
|
||||
txt.print("ok: 5555 >= 322\n")
|
||||
else
|
||||
c64scr.print("error in 5555>=322!\n")
|
||||
txt.print("error in 5555>=322!\n")
|
||||
|
||||
v1 = 322
|
||||
v2 = 322
|
||||
if v1==v2
|
||||
c64scr.print("ok: 322 == 322\n")
|
||||
txt.print("ok: 322 == 322\n")
|
||||
else
|
||||
c64scr.print("error in 322==322!\n")
|
||||
txt.print("error in 322==322!\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("error in 322!=322!\n")
|
||||
txt.print("error in 322!=322!\n")
|
||||
else
|
||||
c64scr.print("ok: 322 is not != 322\n")
|
||||
txt.print("ok: 322 is not != 322\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in 322<322!\n")
|
||||
txt.print("error in 322<322!\n")
|
||||
else
|
||||
c64scr.print("ok: 322 is not < 322\n")
|
||||
txt.print("ok: 322 is not < 322\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: 322 <= 322\n")
|
||||
txt.print("ok: 322 <= 322\n")
|
||||
else
|
||||
c64scr.print("error in 322<=322!\n")
|
||||
txt.print("error in 322<=322!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in 322>322!\n")
|
||||
txt.print("error in 322>322!\n")
|
||||
else
|
||||
c64scr.print("ok: 322 is not > 322\n")
|
||||
txt.print("ok: 322 is not > 322\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: 322 >= 322\n")
|
||||
txt.print("ok: 322 >= 322\n")
|
||||
else
|
||||
c64scr.print("error in 322>=322!\n")
|
||||
|
||||
check_eval_stack()
|
||||
txt.print("error in 322>=322!\n")
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
@ -12,141 +12,130 @@ main {
|
||||
v1 = 100
|
||||
v2 = 30333
|
||||
if v1==v2
|
||||
c64scr.print("error in 100==30333!\n")
|
||||
txt.print("error in 100==30333!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 not == 30333\n")
|
||||
txt.print("ok: 100 not == 30333\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("ok: 100 != 30333\n")
|
||||
txt.print("ok: 100 != 30333\n")
|
||||
else
|
||||
c64scr.print("error in 100!=30333!\n")
|
||||
txt.print("error in 100!=30333!\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("ok: 100 < 30333\n")
|
||||
txt.print("ok: 100 < 30333\n")
|
||||
else
|
||||
c64scr.print("error in 100<30333!\n")
|
||||
txt.print("error in 100<30333!\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: 100 <= 30333\n")
|
||||
txt.print("ok: 100 <= 30333\n")
|
||||
else
|
||||
c64scr.print("error in 100<=30333!\n")
|
||||
txt.print("error in 100<=30333!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in 100>30333!\n")
|
||||
txt.print("error in 100>30333!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 is not >30333\n")
|
||||
txt.print("ok: 100 is not >30333\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("error in 100>=30333!\n")
|
||||
txt.print("error in 100>=30333!\n")
|
||||
else
|
||||
c64scr.print("ok: 100 is not >=30333\n")
|
||||
txt.print("ok: 100 is not >=30333\n")
|
||||
|
||||
|
||||
v1 = 125
|
||||
v2 = -222
|
||||
if v1==v2
|
||||
c64scr.print("error in 125==-222!\n")
|
||||
txt.print("error in 125==-222!\n")
|
||||
else
|
||||
c64scr.print("ok: 125 not == -222\n")
|
||||
txt.print("ok: 125 not == -222\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("ok: 125 != -222\n")
|
||||
txt.print("ok: 125 != -222\n")
|
||||
else
|
||||
c64scr.print("error in 125!=-222!\n")
|
||||
txt.print("error in 125!=-222!\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in 125<-222!\n")
|
||||
txt.print("error in 125<-222!\n")
|
||||
else
|
||||
c64scr.print("ok: 125 is not < -222\n")
|
||||
txt.print("ok: 125 is not < -222\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("error in 125<=-222!\n")
|
||||
txt.print("error in 125<=-222!\n")
|
||||
else
|
||||
c64scr.print("ok: 125 is not <= -222\n")
|
||||
txt.print("ok: 125 is not <= -222\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("ok: 125 > -222\n")
|
||||
txt.print("ok: 125 > -222\n")
|
||||
else
|
||||
c64scr.print("error in 125>-222!\n")
|
||||
txt.print("error in 125>-222!\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: 125 >= -222\n")
|
||||
txt.print("ok: 125 >= -222\n")
|
||||
else
|
||||
c64scr.print("error in 125>=-222!\n")
|
||||
txt.print("error in 125>=-222!\n")
|
||||
|
||||
v1 = -222
|
||||
v2 = -222
|
||||
if v1==v2
|
||||
c64scr.print("ok: -222 == -222\n")
|
||||
txt.print("ok: -222 == -222\n")
|
||||
else
|
||||
c64scr.print("error in -222==-222!\n")
|
||||
txt.print("error in -222==-222!\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("error in -222!=-222!\n")
|
||||
txt.print("error in -222!=-222!\n")
|
||||
else
|
||||
c64scr.print("ok: -222 is not != -222\n")
|
||||
txt.print("ok: -222 is not != -222\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in -222<-222!\n")
|
||||
txt.print("error in -222<-222!\n")
|
||||
else
|
||||
c64scr.print("ok: -222 is not < -222\n")
|
||||
txt.print("ok: -222 is not < -222\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: -222 <= -222\n")
|
||||
txt.print("ok: -222 <= -222\n")
|
||||
else
|
||||
c64scr.print("error in -222<=-222!\n")
|
||||
txt.print("error in -222<=-222!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in -222>-222!\n")
|
||||
txt.print("error in -222>-222!\n")
|
||||
else
|
||||
c64scr.print("ok: -222 is not > -222\n")
|
||||
txt.print("ok: -222 is not > -222\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: -222 >= -222\n")
|
||||
txt.print("ok: -222 >= -222\n")
|
||||
else
|
||||
c64scr.print("error in -222>=-222!\n")
|
||||
txt.print("error in -222>=-222!\n")
|
||||
|
||||
v1 = 1000
|
||||
v2 = 1000
|
||||
if v1==v2
|
||||
c64scr.print("ok: 1000 == 1000\n")
|
||||
txt.print("ok: 1000 == 1000\n")
|
||||
else
|
||||
c64scr.print("error in 1000==1000!\n")
|
||||
txt.print("error in 1000==1000!\n")
|
||||
|
||||
if v1!=v2
|
||||
c64scr.print("error in 1000!=1000!\n")
|
||||
txt.print("error in 1000!=1000!\n")
|
||||
else
|
||||
c64scr.print("ok: 1000 is not != 1000\n")
|
||||
txt.print("ok: 1000 is not != 1000\n")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print("error in 1000<1000!\n")
|
||||
txt.print("error in 1000<1000!\n")
|
||||
else
|
||||
c64scr.print("ok: 1000 is not < 1000\n")
|
||||
txt.print("ok: 1000 is not < 1000\n")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print("ok: 1000 <= 1000\n")
|
||||
txt.print("ok: 1000 <= 1000\n")
|
||||
else
|
||||
c64scr.print("error in 1000<=1000!\n")
|
||||
txt.print("error in 1000<=1000!\n")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print("error in 1000>1000!\n")
|
||||
txt.print("error in 1000>1000!\n")
|
||||
else
|
||||
c64scr.print("ok: 1000 is not > 1000\n")
|
||||
txt.print("ok: 1000 is not > 1000\n")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print("ok: 1000 >= 1000\n")
|
||||
txt.print("ok: 1000 >= 1000\n")
|
||||
else
|
||||
c64scr.print("error in 1000>=1000!\n")
|
||||
|
||||
check_eval_stack()
|
||||
txt.print("error in 1000>=1000!\n")
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -9,7 +9,7 @@ main {
|
||||
byte v2
|
||||
ubyte cr
|
||||
|
||||
c64scr.print("signed byte ")
|
||||
txt.print("signed byte ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
@ -39,65 +39,55 @@ main {
|
||||
; comparisons:
|
||||
v1=-20
|
||||
v2=125
|
||||
c64scr.print("v1=-20, v2=125\n")
|
||||
txt.print("v1=-20, v2=125\n")
|
||||
compare()
|
||||
|
||||
v1=80
|
||||
v2=80
|
||||
c64scr.print("v1 = v2 = 80\n")
|
||||
txt.print("v1 = v2 = 80\n")
|
||||
compare()
|
||||
|
||||
v1=20
|
||||
v2=-111
|
||||
c64scr.print("v1=20, v2=-111\n")
|
||||
txt.print("v1=20, v2=-111\n")
|
||||
compare()
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
c64scr.print(" == != < > <= >=\n")
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
@ -10,7 +10,7 @@ main {
|
||||
float v2
|
||||
ubyte cr
|
||||
|
||||
c64scr.print("floating point ")
|
||||
txt.print("floating point ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
@ -40,79 +40,71 @@ main {
|
||||
; comparisons:
|
||||
v1=20
|
||||
v2=666.66
|
||||
c64scr.print("v1=20, v2=666.66\n")
|
||||
txt.print("v1=20, v2=666.66\n")
|
||||
compare()
|
||||
|
||||
v1=-20
|
||||
v2=666.66
|
||||
c64scr.print("v1=-20, v2=666.66\n")
|
||||
txt.print("v1=-20, v2=666.66\n")
|
||||
compare()
|
||||
|
||||
v1=666.66
|
||||
v2=555.55
|
||||
c64scr.print("v1=666.66, v2=555.55\n")
|
||||
txt.print("v1=666.66, v2=555.55\n")
|
||||
compare()
|
||||
|
||||
v1=3.1415
|
||||
v2=-3.1415
|
||||
c64scr.print("v1 = 3.1415, v2 = -3.1415\n")
|
||||
txt.print("v1 = 3.1415, v2 = -3.1415\n")
|
||||
compare()
|
||||
|
||||
v1=3.1415
|
||||
v2=3.1415
|
||||
c64scr.print("v1 = v2 = 3.1415\n")
|
||||
txt.print("v1 = v2 = 3.1415\n")
|
||||
compare()
|
||||
|
||||
v1=0
|
||||
v2=0
|
||||
c64scr.print("v1 = v2 = 0\n")
|
||||
txt.print("v1 = v2 = 0\n")
|
||||
compare()
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
c64scr.print(" == != < > <= >=\n")
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
%import c64utils
|
||||
%import c64textio
|
||||
%zeropage basicsafe
|
||||
|
||||
main {
|
||||
@ -9,7 +9,7 @@ main {
|
||||
ubyte v2
|
||||
ubyte cr
|
||||
|
||||
c64scr.print("unsigned byte ")
|
||||
txt.print("unsigned byte ")
|
||||
|
||||
cr=v1==v2
|
||||
cr=v1==v2
|
||||
@ -39,65 +39,56 @@ main {
|
||||
; comparisons:
|
||||
v1=20
|
||||
v2=199
|
||||
c64scr.print("v1=20, v2=199\n")
|
||||
txt.print("v1=20, v2=199\n")
|
||||
compare()
|
||||
|
||||
v1=80
|
||||
v2=80
|
||||
c64scr.print("v1 = v2 = 80\n")
|
||||
txt.print("v1 = v2 = 80\n")
|
||||
compare()
|
||||
|
||||
v1=220
|
||||
v2=10
|
||||
c64scr.print("v1=220, v2=10\n")
|
||||
txt.print("v1=220, v2=10\n")
|
||||
compare()
|
||||
|
||||
check_eval_stack()
|
||||
return
|
||||
|
||||
sub compare() {
|
||||
c64scr.print(" == != < > <= >=\n")
|
||||
txt.print(" == != < > <= >=\n")
|
||||
|
||||
if v1==v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
if v1!=v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1<=v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
|
||||
if v1>=v2
|
||||
c64scr.print(" Q ")
|
||||
txt.print(" Q ")
|
||||
else
|
||||
c64scr.print(" . ")
|
||||
txt.print(" . ")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub check_eval_stack() {
|
||||
if X!=255 {
|
||||
c64scr.print("x=")
|
||||
c64scr.print_ub(X)
|
||||
c64scr.print(" error!\n")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user