Compare commits

...

159 Commits
v4.0 ... v4.4

Author SHA1 Message Date
22031f39b0 update compiled examples 2020-10-02 23:39:20 +02:00
c4673d3a67 v4.4 2020-10-02 23:32:45 +02:00
e83e021541 doc 2020-10-02 23:31:49 +02:00
c1f2ecd413 struct assignment from array value now checks number of elements 2020-10-02 22:48:39 +02:00
46fbe01df9 added codengeration for assigment of array of values to a struct variable (all members at once) 2020-10-02 22:37:52 +02:00
8647a8290e fix code generation for using struct vars in arrays and such 2020-10-02 22:21:18 +02:00
bac51f4b31 fix subtraction error for bytes 2020-10-02 21:30:32 +02:00
582aab180a oops 2020-10-02 02:39:19 +02:00
5fb714fcb2 expression splitter integrated into expression simplifier 2020-10-02 01:54:37 +02:00
3994de77d0 fix expression splitter handling related to code ballooning 2020-10-02 01:49:55 +02:00
24c8d1f1f4 expression splitter for vardecls with binexpr init expression 2020-10-02 00:34:12 +02:00
110f877dcc binexpr expression splitter for assignments 2020-10-02 00:04:21 +02:00
9cd3a9f8e8 fix isSameAs for ArrayIndexed expressions, and by extension, assignment.isAugmentable() 2020-10-01 23:26:43 +02:00
1464050bf5 expression splitter moved to separate optimizer 2020-10-01 02:58:12 +02:00
95e9e1b550 avoid adding unneeded variable initalization assignments. Improved removal of useless double assignments. 2020-10-01 00:39:49 +02:00
bda1c1c1eb reduce slow estack usage by splitting up simple binary expressions 2020-09-30 19:57:16 +02:00
d020a7974a reduce slow estack usage by splitting up simple binary expressions 2020-09-30 17:51:35 +02:00
a51fad3aab parentheses around binary exprs in source output 2020-09-30 16:38:54 +02:00
3cd32778bb don't split expressions referencing the target variable wrongly 2020-09-30 01:11:33 +02:00
8d67056f84 fixed estack corruption caused by c64 print_f 2020-09-29 21:12:16 +02:00
e986973b5e wrong floats 2020-09-29 04:05:45 +02:00
448c934cba optimized neg(x) and abs(x) 2020-09-29 03:58:17 +02:00
96ef7ba55d fixed ast to source for structs 2020-09-29 00:28:11 +02:00
4372de1e7e allow creating arrays of pointers to other arrays. Usefullness is very limited though... 2020-09-29 00:03:47 +02:00
af0fb88adf allow creating string arrays. Fixed array index scaling for word arrays. 2020-09-28 02:23:36 +02:00
066233eee8 todos 2020-09-27 22:05:44 +02:00
b6f85d10b0 reintroduced system reset at program exit if zeropage is clobbered 2020-09-27 22:00:36 +02:00
6f75413c09 some more optimizations in expressions with memreads 2020-09-27 21:43:40 +02:00
d45fe4ce74 fixed invalid eval stack ptr issue 2020-09-27 20:55:34 +02:00
e828c013e6 fix word+/-byte errors if byte was unsigned 2020-09-27 20:23:42 +02:00
988459f744 don't generate a byte storage for every single time a register needs saving 2020-09-27 16:26:02 +02:00
7c701bdf3f corrections 2020-09-27 14:14:45 +02:00
446fc35d5c avoid excessive comparisons for certain comparison expressions against zero 2020-09-27 03:55:59 +02:00
bec9cc7047 asm store/load same optimizer back.... 2020-09-27 02:45:59 +02:00
961380acb6 optimized float ==0 or 1 comparisons 2020-09-27 01:56:08 +02:00
84c0685a60 fix faulty comparison optimization 2020-09-27 01:40:12 +02:00
629222f103 larger 2020-09-26 19:59:57 +02:00
8c448e5bc2 finished optimized comparison asm generation 2020-09-26 19:55:04 +02:00
b5fa6c2d0a library modules imported from embedded resource now contain proper file path (useful for error messages) 2020-09-26 19:30:17 +02:00
680b2df08a just call the asmsub 2020-09-26 19:14:06 +02:00
09bd47f98b > 2020-09-26 19:02:29 +02:00
7f69f9ce4f <= 2020-09-26 18:04:43 +02:00
4179b4e543 all unsigned comparisons 2020-09-26 17:45:35 +02:00
66364554c4 new comparisons testprog 2020-09-26 16:11:47 +02:00
43f2448789 added (u)byte and (u)word '>' 2020-09-26 13:15:03 +02:00
130cee1e70 tweak '<' code 2020-09-26 12:47:40 +02:00
b976360248 fix fallthrough problem with 'when'. Fix too greedy asm optimization that caused conditional jumps to fail sometimes because the condition value wasn't loaded. 2020-09-26 00:22:55 +02:00
225bfc4164 fix 16+8 bit add and sub sign extensions 2020-09-25 22:51:59 +02:00
d7ceda4d82 removed the automatic system reset at program exit, this did't work with the new init code 2020-09-25 22:12:14 +02:00
14d091e60a crashes :( 2020-09-24 23:50:20 +02:00
2809668ef4 new asm code for (u)word and (u)byte < 2020-09-24 23:08:36 +02:00
bafb86e00b new asm code for (n)equals 2020-09-24 22:28:24 +02:00
f5db31b8ff do..until condition can now refer to variables defined in the loop's inner scope. 2020-09-24 19:26:07 +02:00
e1d0dbed0c do..until condition can now refer to variables defined in the loop's inner scope. 2020-09-23 23:24:32 +02:00
1d1fe364d0 added %option no_sysinit to avoid having the system re-initialization code executed at the start of the program 2020-09-23 23:01:47 +02:00
2b9316c4ff reworked program init logic so that it is included as the first thing inside main.start itself, to allow better stand alone asm 2020-09-23 22:29:21 +02:00
c50cbbb526 typo 2020-09-23 18:50:32 +02:00
b93d9ecd7e memtop cx16 2020-09-23 02:34:49 +02:00
96243db88b refresh compiled examples 2020-09-23 00:29:40 +02:00
4daf75a8cc better checks for invalid %output and %launcher values. Added diskdir examples. 2020-09-23 00:22:36 +02:00
8c63d7cf5b diskdir 2020-09-22 23:22:20 +02:00
6f78a32e64 diskdir 2020-09-22 23:12:43 +02:00
af6731c9c8 preparing version 4.3 2020-09-22 21:50:56 +02:00
25cf0d2b94 don't suggest a mult replacement routine to be used, faster ones are likely to require large tables 2020-09-22 21:19:01 +02:00
9389791d91 created own circle and disc subroutines for cx16 because its rom routine is not yet implemented and just does a BRK 2020-09-22 02:52:09 +02:00
aa8191d0a1 introduced graphics module wrapper for cx16 to make even more programs compatible 2020-09-22 02:21:16 +02:00
0d5c78e875 introduced graphics module wrapper for cx16 to make even more programs compatible 2020-09-22 02:12:01 +02:00
e8679ae03b fixed print_f on cx16. Some more examples are now multi-platform. 2020-09-22 01:45:51 +02:00
d1d224b7fc fixed print_f on cx16. Some more examples are now multi-platform. 2020-09-22 01:34:05 +02:00
df995f7bc9 fixed float zp problem on C64, added more zp locations to block list 2020-09-22 01:05:07 +02:00
af39502450 doc 2020-09-22 00:47:02 +02:00
ffa38955d6 improved scroll_down and scroll_up to use VERA dual data ports instead of a copybuffer 2020-09-22 00:34:43 +02:00
8d82fb6d8f added cx16 txt.scroll_right 2020-09-22 00:00:22 +02:00
306770331a added cx16 txt.scroll_left 2020-09-21 23:39:25 +02:00
d3f433c8cf specify VERA data port to use 2020-09-21 23:04:01 +02:00
cf49cbd1f8 more consistent about the system reset routine 2020-09-21 22:35:07 +02:00
8a99e75299 added cx16 txt.scroll_down 2020-09-21 22:06:48 +02:00
2dbf849c82 added cx16 txt.scroll_up 2020-09-21 21:39:36 +02:00
ba3dce0b4c optimized cx16 txt screen functions to use VERA autoincrement 2020-09-21 19:30:21 +02:00
ca9588380a added cx16 txt.clear_screencolors 2020-09-21 18:42:28 +02:00
ae2619602d lib renames in docs 2020-09-21 18:21:24 +02:00
de06353194 auto select correct library to import based on target, instead of having c64- and cx16- prefix variants
some programs are now 100% source compatible between C64 and Cx16 targets!
import libraries have been rena;med
2020-09-21 00:50:09 +02:00
3ff3f5e1cc compiler errors in standard format so that you can click on them in IDE to jump to the line 2020-09-20 22:24:35 +02:00
4b747859b3 types of constant values now actually follow their declared const var type 2020-09-20 01:14:53 +02:00
2201765366 mult fixes 2020-09-20 00:17:33 +02:00
dfa1d5e398 removed the ".w" word suffix (it confused the parser). 2020-09-19 23:27:40 +02:00
ce9a90f626 updates to make c16txtio more complete 2020-09-19 23:00:47 +02:00
2deb18beb2 tweaks to c64 txtio. Fixed expression evaluation of bitwise invert. 2020-09-19 22:37:24 +02:00
0f7454059c tweaks to c64 txtio 2020-09-19 22:10:33 +02:00
f9ba09ac4d todo 2020-09-19 17:39:46 +02:00
4e74873eae better swap() code 2020-09-19 17:32:29 +02:00
f0cd03d14f removed invalid duplicate name check about subroutine parameters 2020-09-19 16:04:04 +02:00
f2b069c562 correction, we don't allow address-of as a value for memory mapped vars, improved the error message instead 2020-09-19 15:54:42 +02:00
bc89306dc1 better detection of duplicate variable definitions 2020-09-19 15:46:51 +02:00
bf4da1655b doc 2020-09-18 23:57:40 +02:00
d819aa270f test 2020-09-18 23:38:50 +02:00
e6d945f835 doc 2020-09-18 23:35:02 +02:00
4fe408f1fd doc 2020-09-18 23:34:32 +02:00
c376e42092 implemented hidden line removal 2020-09-18 23:15:08 +02:00
63a653cdf0 preparing for hidden line removal 2020-09-18 22:51:44 +02:00
5d900800f2 vardecl value inits must not be shuffled around but stay at their original line at all times 2020-09-18 22:24:26 +02:00
def06dbc0b allow address-of to be used as a value for a memory pointer variable 2020-09-18 22:10:20 +02:00
9b66a597bb array literal const check added 2020-09-18 21:30:59 +02:00
f1ee3b4e60 version 4.2 2020-09-16 23:04:18 +02:00
6395e39d63 avoid generating superfluous '0' variable initializations, and fix erroneous vardecl order shifting 2020-09-16 22:15:06 +02:00
2a6d9d7e31 more optimal codegen for some typecasts 2020-09-15 03:26:57 +02:00
32a7cd31da more optimal codegen for if statements 2020-09-15 00:31:44 +02:00
dd4a56cb5f cx16 safe clobbers for now 2020-09-15 00:14:36 +02:00
d110d1cb5f c64 system reset now banks kernel rom back in 2020-09-15 00:10:20 +02:00
48858019b7 added the last of the optimized mul_word asm routines 2020-09-14 23:54:01 +02:00
aff6b1fca5 added some more optimized mul_word asm routines 2020-09-14 23:03:18 +02:00
d260182ef3 added some more optimized mul_byte asm routines 2020-09-14 22:06:40 +02:00
e39a38b0d9 things 2020-09-13 21:04:51 +02:00
82d7179c92 printf now uses proper zp addressing 2020-09-13 21:01:19 +02:00
f42746ba06 reg_x removal: c64textio and c64lib. last one. 2020-09-13 20:52:29 +02:00
1f69deaccd reg_x removal: c64floats 2020-09-13 20:44:55 +02:00
ea8b7ab193 reg_x removal: math.asm and some others 2020-09-13 20:38:50 +02:00
9938959026 reg_x removal: prog8lib 2020-09-13 20:25:30 +02:00
d5e5485d2e fixed estack X corruption in float augmented assignments 2020-09-13 19:44:03 +02:00
97b9c8f320 don't clobber A when trying to save X at functioncall 2020-09-12 19:04:44 +02:00
35aebbc209 optimize unneeded type casts for register args 2020-09-12 02:48:16 +02:00
81f7419f70 fix X register clobbering in asmfunc call, fixed graphics.plot() 2020-09-12 01:23:56 +02:00
2f951bd54d tweaking cobra mk3 2020-09-11 19:46:11 +02:00
18f5963b09 cobra mk3 2020-09-10 01:31:21 +02:00
836509c1d1 mult todos. 2020-09-10 00:53:35 +02:00
949d536e42 mult todo's. Fixed wrong compilation target when compiling multiple files at once. 2020-09-10 00:26:35 +02:00
f69b17e165 mult todo's 2020-09-10 00:07:06 +02:00
49a0584c54 added a %target directive 2020-09-09 22:53:34 +02:00
e21aa2c8f0 better naming of the optimized math mult routines 2020-09-09 22:16:37 +02:00
40071b1431 fix compiler crash with adding too many typecasts to args. useless lsb() and msb() are optimized away. 2020-09-09 21:37:56 +02:00
02e29e6990 added some preliminary clobber specs to some cx16 graphics calls, This fixes the 3d cube gfx 2020-09-07 04:06:46 +02:00
e19de0901e Fix cx16 system reset. Added cx16 VIA registers. Fix cx16 VERA register widths. 2020-09-07 03:09:09 +02:00
137d506e42 improve register arg passing again 2020-09-07 02:29:03 +02:00
90c4a26d52 we don't implement asmsub params via @stack yet 2020-09-07 01:24:10 +02:00
f378a8997b improved ability to use register X in asm subroutine fuction arguments 2020-09-07 00:25:51 +02:00
1377bed988 fix assembly for cx16 when zp is not basicsafe 2020-09-06 17:58:05 +02:00
8f9f947c42 fix some issues with float const 0.0 and 1.0 2020-09-05 02:07:41 +02:00
37f6c2858f warning about attempt to put floats in zp 2020-09-05 01:45:58 +02:00
13d7f239ab floating point 1.0 no longer referenced from ROM because cx16 doesn't have it. Added some more cx16 examples. 2020-09-05 00:17:58 +02:00
a6f3c84e28 fix cx16 word sign extend in bitshift 2020-09-04 22:38:03 +02:00
fe4e0e9835 cleanups 2020-08-31 23:00:53 +02:00
809917f13b version 4.1 2020-08-31 21:44:38 +02:00
2b35498370 added CX16 txt.setcc and swirl examples that use it 2020-08-31 21:01:18 +02:00
f45eabdd9e added CX16 VERA registers, made txt.fill_screen work on CX16 2020-08-31 18:23:52 +02:00
438f3ee8d2 make GIVUAYFAY work (unsigned word to float) 2020-08-31 17:16:51 +02:00
4bea31f051 fl_zero fix 2020-08-31 01:04:04 +02:00
5eae7a2b93 tweak mandelbrots and c64 graphics plot() doesnt work with XY parameter 2020-08-31 00:36:40 +02:00
364ef3e55c tweak cx16 mandelbrots 2020-08-31 00:03:05 +02:00
e61818f194 tweak cx16 mandelbrots 2020-08-30 19:31:20 +02:00
0f9ce319d4 readme 2020-08-30 18:36:02 +02:00
5d90871789 got floating points working in commanderx16, added txt.color() to set text color 2020-08-30 00:15:18 +02:00
88a9e09918 got floating points working in commanderx16 2020-08-29 23:55:26 +02:00
c50ecf6055 fix for loop asm creation with word loopvar 2020-08-29 02:05:24 +02:00
a18de75da9 fix compiler loop and missing type checks on for loop range values 2020-08-29 01:48:41 +02:00
e112dfd910 implemented signed byte and word division 2020-08-29 00:00:53 +02:00
9154d8bd37 optimizing X register saving for 65c02 using phx/plx instead of zp location 2020-08-28 22:11:33 +02:00
0b55372b3b cleanup cx16 things and added call signatures. c64graphics moved into built-in libraries. 2020-08-28 21:42:53 +02:00
3ad7fb010f clearer about emulator 2020-08-27 21:09:59 +02:00
3f64d1bb5a oops. 2020-08-27 21:04:08 +02:00
159 changed files with 8236 additions and 3149 deletions

View File

@ -12,9 +12,18 @@ Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
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
@ -29,7 +38,7 @@ 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 a modern PC to do the work on
- very quick compilation times
@ -37,17 +46,16 @@ Rapid edit-compile-run-debug cycle:
- 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
Prog8 is mainly targeted at the Commodore-64 machine.
Preliminary support for the [CommanderX16](https://www.commanderx16.com) is available as a second compilation target.
Contributions to improve these or to add support for other machines are welcome!
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
- "c64": Commodore-64 (6510 CPU = almost a 6502) premium support.
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) experimental support.
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
Documentation/manual
--------------------
https://prog8.readthedocs.io/
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.
@ -67,16 +75,18 @@ Example code
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64textio
%import textio
%zeropage basicsafe
main {
ubyte[256] sieve
ubyte candidate_prime = 2
ubyte candidate_prime = 2 ; is increased in the loop
sub start() {
memset(sieve, 256, false) ; clear the sieve
; clear the sieve, to reset starting situation on subsequent runs
memset(sieve, 256, false)
; calculate primes
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0
repeat {
@ -87,22 +97,23 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
txt.print(", ")
amount++
}
c64.CHROUT('\n')
txt.chrout('\n')
txt.print("number of primes (expected 54): ")
txt.print_ub(amount)
c64.CHROUT('\n')
txt.chrout('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0 ; we wrapped; no more primes available
return 0 ; we wrapped; no more primes available in the sieve
}
; found next one, mark the multiples and return it.
sieve[candidate_prime] = true
uword multiple = candidate_prime
while multiple < len(sieve) {
sieve[lsb(multiple)] = true
multiple += candidate_prime
@ -112,11 +123,11 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
}
when compiled an ran on a C-64 you'll get:
![c64 screen](docs/source/_static/primes_example.png)
One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this:
![wizzine screen](docs/source/_static/wizzine.png)
@ -128,3 +139,8 @@ Another example (cube3d-sprites.p8) draws the vertices of a rotating 3d cube:
If you want to play a video game, a fully working Tetris clone is included in the examples:
![tehtriz_screen](docs/source/_static/tehtriz.png)
The CommanderX16 compiler target is quite capable already too, here's a well known space ship
animated in 3D with hidden line removal, in the CommanderX16 emulator:
![cobra3d](docs/source/_static/cobra3d.png)

View File

@ -1,11 +1,11 @@
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
}
}
plugins {
// id "org.jetbrains.kotlin.jvm" version "1.4.0"
// id "org.jetbrains.kotlin.jvm" version "1.4.10"
id 'application'
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.2.0'

View File

@ -1,25 +1,31 @@
; --- low level floating point assembly routines for the C64
FL_ONE_const .byte 129 ; 1.0
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
floats_store_reg .byte 0 ; temp storage
ub2float .proc
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy P8ZP_SCRATCH_B1
jsr FREADUY
lda #0
jsr GIVAYF
_fac_to_mem ldx P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
jsr MOVMF
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
.pend
b2float .proc
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_B1
@ -29,7 +35,7 @@ b2float .proc
uw2float .proc
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_W1
@ -40,7 +46,7 @@ uw2float .proc
w2float .proc
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy P8ZP_SCRATCH_W1
@ -53,7 +59,7 @@ stack_b2float .proc
; -- b2float operating on the stack
inx
lda P8ESTACK_LO,x
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr FREADSA
jmp push_fac1_as_result
.pend
@ -63,7 +69,7 @@ stack_w2float .proc
inx
ldy P8ESTACK_LO,x
lda P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr GIVAYF
jmp push_fac1_as_result
.pend
@ -72,9 +78,10 @@ stack_ub2float .proc
; -- ub2float operating on the stack
inx
lda P8ESTACK_LO,x
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
tay
jsr FREADUY
lda #0
jsr GIVAYF
jmp push_fac1_as_result
.pend
@ -83,16 +90,16 @@ stack_uw2float .proc
inx
lda P8ESTACK_LO,x
ldy P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY
jmp push_fac1_as_result
.pend
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr AYINT
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
lda $64
sta P8ESTACK_HI,x
lda $65
@ -103,9 +110,9 @@ stack_float2w .proc ; also used for float2b
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr GETADR
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
sta P8ESTACK_HI,x
tya
sta P8ESTACK_LO,x
@ -229,15 +236,15 @@ inc_var_f .proc
; -- add 1 to float pointed to by A/Y
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr MOVFM
lda #<FL_FONE
ldy #>FL_FONE
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr FADD
ldx P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr MOVMF
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
.pend
@ -245,9 +252,9 @@ dec_var_f .proc
; -- subtract 1 from float pointed to by A/Y
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_REG_X
lda #<FL_FONE
ldy #>FL_FONE
stx P8ZP_SCRATCH_REG
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr MOVFM
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
@ -255,7 +262,7 @@ dec_var_f .proc
ldx P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr MOVMF
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
.pend
@ -284,7 +291,7 @@ push_fac1_as_result .proc
jsr MOVMF
lda #<fmath_float1
ldy #>fmath_float1
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
jmp push_float
.pend
@ -296,21 +303,21 @@ pow_f .proc
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr CONUPK ; fac2 = float1
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
jmp push_fac1_as_result
.pend
div_f .proc
; -- push f1/f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FDIV
@ -320,7 +327,7 @@ div_f .proc
add_f .proc
; -- push f1+f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FADD
@ -330,7 +337,7 @@ add_f .proc
sub_f .proc
; -- push f1-f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FSUB
@ -340,7 +347,7 @@ sub_f .proc
mul_f .proc
; -- push f1*f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FMULT
@ -348,19 +355,19 @@ mul_f .proc
.pend
neg_f .proc
; -- push -flt back on stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
jsr NEGOP
jmp push_fac1_as_result
; -- toggle the sign bit on the stack
lda P8ESTACK_HI+3,x
eor #$80
sta P8ESTACK_HI+3,x
rts
.pend
abs_f .proc
; -- push abs(float) on stack (as float)
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
jsr ABS
jmp push_fac1_as_result
; -- strip the sign bit on the stack
lda P8ESTACK_HI+3,x
and #$7f
sta P8ESTACK_HI+3,x
rts
.pend
equal_f .proc
@ -464,7 +471,7 @@ _return_true lda #1
func_sin .proc
; -- push sin(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr SIN
jmp push_fac1_as_result
.pend
@ -472,7 +479,7 @@ func_sin .proc
func_cos .proc
; -- push cos(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr COS
jmp push_fac1_as_result
.pend
@ -480,7 +487,7 @@ func_cos .proc
func_tan .proc
; -- push tan(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr TAN
jmp push_fac1_as_result
.pend
@ -488,7 +495,7 @@ func_tan .proc
func_atan .proc
; -- push atan(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr ATN
jmp push_fac1_as_result
.pend
@ -496,7 +503,7 @@ func_atan .proc
func_ln .proc
; -- push ln(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr LOG
jmp push_fac1_as_result
.pend
@ -504,7 +511,7 @@ func_ln .proc
func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr LOG
jsr MOVEF
lda #<c64.FL_LOG2
@ -516,7 +523,7 @@ func_log2 .proc
func_sqrt .proc
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr SQR
jmp push_fac1_as_result
.pend
@ -524,7 +531,7 @@ func_sqrt .proc
func_rad .proc
; -- convert degrees to radians (d * pi / 180)
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda #<_pi_div_180
ldy #>_pi_div_180
jsr FMULT
@ -535,7 +542,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 P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr FMULT
@ -545,7 +552,7 @@ _one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
func_round .proc
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr FADDH
jsr INT
jmp push_fac1_as_result
@ -553,7 +560,7 @@ func_round .proc
func_floor .proc
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
jsr INT
jmp push_fac1_as_result
.pend
@ -561,7 +568,7 @@ func_floor .proc
func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
@ -571,8 +578,8 @@ func_ceil .proc
jsr FCOMP
cmp #0
beq +
lda #<FL_FONE
ldy #>FL_FONE
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr FADD
+ jmp push_fac1_as_result
.pend
@ -628,7 +635,7 @@ func_max_f .proc
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx P8ZP_SCRATCH_REG_X
stx floats_store_reg
- sty P8ZP_SCRATCH_REG
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
@ -648,6 +655,8 @@ _minmax_cmp cmp #255 ; modified
dey
cpy #255
bne -
ldx floats_store_reg
stx P8ZP_SCRATCH_REG
jmp push_fac1_as_result
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend
@ -663,11 +672,11 @@ _largest_pos_float .byte 255,127,255,255,255 ; largest positive float
.pend
func_sum_f .proc
lda #<FL_ZERO
ldy #>FL_ZERO
lda #<FL_ZERO_const
ldy #>FL_ZERO_const
jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx P8ZP_SCRATCH_REG_X
stx floats_store_reg
- sty P8ZP_SCRATCH_REG
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
@ -683,7 +692,9 @@ func_sum_f .proc
bcc -
inc P8ZP_SCRATCH_W1+1
bne -
+ jmp push_fac1_as_result
+ ldx floats_store_reg
stx P8ZP_SCRATCH_REG
jmp push_fac1_as_result
.pend
sign_f .proc

View File

@ -4,14 +4,14 @@
;
; indent format: TABS, size=8
%target c64
%option enable_floats
c64flt {
floats {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
; ---- C64 basic and kernal ROM float constants and functions ----
@ -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
@ -52,22 +50,22 @@ romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; there is also c64flt.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
@ -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
@ -193,33 +192,26 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
}
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 P8ZP_SCRATCH_REG_X
stx floats_store_reg
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 P8ZP_SCRATCH_REG_X
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
jsr c64.CHROUT
iny
bne -
+ ldx floats_store_reg
rts
}}
}
sub print_fln (float value) {
; ---- prints the floating point value (with a newline at the end) using basic rom routines
%asm {{
stx P8ZP_SCRATCH_REG_X
lda #<value
ldy #>value
jsr MOVFM ; load float into fac1
jsr FPRINTLN ; print fac1 with newline
ldx P8ZP_SCRATCH_REG_X
rts
}}
%asminclude "library:c64/floats.asm", ""
}
%asminclude "library:c64floats.asm", ""
} ; ------ end of block c64flt

View File

@ -0,0 +1,244 @@
%target c64
%import textio
; bitmap pixel graphics module for the C64
; only black/white monchrome 320x200 for now
; assumes bitmap screen memory is $2000-$3fff
graphics {
const uword BITMAP_ADDRESS = $2000
const uword WIDTH = 320
const ubyte HEIGHT = 200
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
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
word @zp dy = y2-y1
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 xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm, filled
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
while xx>=yy {
ubyte ycenter_plus_yy = ycenter + yy
ubyte ycenter_min_yy = ycenter - yy
ubyte ycenter_plus_xx = ycenter + xx
ubyte ycenter_min_xx = ycenter - xx
for internal_plotx in xcenter to xcenter+xx {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
}
for internal_plotx in xcenter-xx to xcenter-1 {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
}
for internal_plotx in xcenter to xcenter+yy {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_min_xx)
}
for internal_plotx in xcenter-yy to xcenter {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_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]
; }
asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
%asm {{
stx internal_plotx
sty internal_plotx+1
jmp internal_plot
}}
}
; for efficiency of internal algorithms here is the internal plot routine
; that takes the plotx coordinate in a separate variable instead of the XY register pair:
uword internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
asmsub internal_plot(ubyte ploty @A) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
%asm {{
tay
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
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
}}
}
}

View File

@ -5,11 +5,13 @@
;
; indent format: TABS, size=8
%target c64
c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $90 ; kernel status variable for I/O
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
@ -249,8 +251,7 @@ asmsub init_system() {
sta c64.COLOR
lda #0
sta c64.BGCOL0
tax
tay
jsr disable_runstop_and_charsetswitch
clc
clv
cli
@ -258,6 +259,26 @@ asmsub init_system() {
}}
}
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
%asm {{
sei
lda #14
sta $01 ; bank the kernal in
jmp (c64.RESET_VEC)
}}
}
asmsub disable_runstop_and_charsetswitch() {
%asm {{
lda #$80
sta 657 ; disable charset switching
lda #239
sta 808 ; disable run/stop key
rts
}}
}
asmsub set_irqvec_excl() clobbers(A) {
%asm {{
sei
@ -298,8 +319,6 @@ _irq_handler_init
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
@ -324,8 +343,6 @@ _irq_handler_end
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
@ -340,7 +357,6 @@ _irq_handler_end
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

View File

@ -4,15 +4,23 @@
;
; indent format: TABS, size=8
%import c64lib
%target c64
%import syslib
%import conv
txt {
asmsub clear_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- clear the character screen with the given fill character and character color.
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
sub clear_screen() {
clear_screenchars(' ')
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
%asm {{
@ -56,13 +64,25 @@ _loop sta c64.Colors,y
}}
}
asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
sub color (ubyte txtcol) {
c64.COLOR = txtcol
}
sub lowercase() {
c64.VMCSB |= 2
}
sub uppercase() {
c64.VMCSB &= ~2
}
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
@ -90,17 +110,17 @@ _scroll_screen ; scroll the screen memory
dey
bpl -
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
@ -124,17 +144,17 @@ _scroll_screen ; scroll the screen memory
dex
bpl -
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
@ -158,17 +178,17 @@ _scroll_screen ; scroll the screen memory
dex
bpl -
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
@ -192,11 +212,13 @@ _scroll_screen ; scroll the screen memory
dex
bpl -
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace
@ -218,7 +240,7 @@ asmsub print (str text @ AY) clobbers(A,Y) {
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
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
pha
tya
@ -227,7 +249,7 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
jsr c64.CHROUT
txa
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
}}
}
@ -235,7 +257,7 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
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
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
_print_byte_digits
pha
@ -252,7 +274,7 @@ _print_byte_digits
jsr c64.CHROUT
_ones txa
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
}}
}
@ -260,7 +282,7 @@ _ones txa
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
stx P8ZP_SCRATCH_REG
pha
cmp #0
bpl +
@ -268,16 +290,14 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
jsr c64.CHROUT
+ pla
jsr conv.byte2decimal
jsr print_ub._print_byte_digits
ldx P8ZP_SCRATCH_REG_X
rts
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 {{
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
bcc +
pha
lda #'$'
@ -287,7 +307,7 @@ asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
jsr c64.CHROUT
tya
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
}}
}
@ -295,7 +315,7 @@ asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
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
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
@ -308,7 +328,7 @@ asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
+ jsr c64.CHROUT
dey
bne -
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
}}
}
@ -341,7 +361,7 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
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
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
@ -349,7 +369,7 @@ asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
jsr c64.CHROUT
iny
bne -
+ ldx P8ZP_SCRATCH_REG_X
+ ldx P8ZP_SCRATCH_REG
rts
}}
}
@ -357,9 +377,9 @@ asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
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
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
@ -422,21 +442,22 @@ asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
}}
}
asmsub setchr (ubyte col @Y, ubyte row @A) clobbers(A) {
; ---- set the character in SCRATCH_ZPB1 on the screen matrix at the given position
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
; ---- sets the character in the screen matrix at the given position
%asm {{
sty P8ZP_SCRATCH_REG
pha
tya
asl a
tay
lda _screenrows+1,y
sta _mod+2
lda _screenrows,y
txa
clc
adc P8ZP_SCRATCH_REG
adc _screenrows,y
sta _mod+1
bcc +
inc _mod+2
+ lda P8ZP_SCRATCH_B1
+ pla
_mod sta $ffff ; modified
rts
@ -444,17 +465,18 @@ _screenrows .word $0400 + range(0, 1000, 40)
}}
}
asmsub getchr (ubyte col @Y, ubyte row @A) clobbers(Y) -> ubyte @ A {
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
%asm {{
sty P8ZP_SCRATCH_B1
pha
tya
asl a
tay
lda setchr._screenrows+1,y
sta _mod+2
lda setchr._screenrows,y
pla
clc
adc P8ZP_SCRATCH_B1
adc setchr._screenrows,y
sta _mod+1
bcc _mod
inc _mod+2
@ -463,21 +485,22 @@ _mod lda $ffff ; modified
}}
}
asmsub setclr (ubyte col @Y, ubyte row @A) clobbers(A) {
; ---- set the color in SCRATCH_ZPB1 on the screen matrix at the given position
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
; ---- set the color in A on the screen matrix at the given position
%asm {{
sty P8ZP_SCRATCH_REG
pha
tya
asl a
tay
lda _colorrows+1,y
sta _mod+2
lda _colorrows,y
txa
clc
adc P8ZP_SCRATCH_REG
adc _colorrows,y
sta _mod+1
bcc +
inc _mod+2
+ lda P8ZP_SCRATCH_B1
+ pla
_mod sta $ffff ; modified
rts
@ -485,17 +508,18 @@ _colorrows .word $d800 + range(0, 1000, 40)
}}
}
asmsub getclr (ubyte col @Y, ubyte row @A) clobbers(Y) -> ubyte @ A {
asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the color in the screen color matrix at the given location
%asm {{
sty P8ZP_SCRATCH_B1
pha
tya
asl a
tay
lda setclr._colorrows+1,y
sta _mod+2
lda setclr._colorrows,y
pla
clc
adc P8ZP_SCRATCH_B1
adc setclr._colorrows,y
sta _mod+1
bcc _mod
inc _mod+2
@ -504,7 +528,7 @@ _mod lda $ffff ; modified
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
lda row
@ -524,7 +548,7 @@ sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
inc _colormod+2
+ lda char
_charmod sta $ffff ; modified
lda color
lda charcolor
_colormod sta $ffff ; modified
rts
}}
@ -533,13 +557,31 @@ _colormod sta $ffff ; modified
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
stx P8ZP_SCRATCH_REG
tax
clc
jsr c64.PLOT
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
; -- returns the text screen width (number of columns)
%asm {{
jsr c64.SCREEN
txa
rts
}}
}
asmsub height() clobbers(X, Y) -> ubyte @A {
; -- returns the text screen height (number of rows)
%asm {{
jsr c64.SCREEN
tya
rts
}}
}
}

View File

@ -213,7 +213,7 @@ asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
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
stx P8ZP_SCRATCH_REG
pha
and #$0f
tax
@ -225,7 +225,7 @@ asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
lsr a
tax
lda _hex_digits,x
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well

View 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
%target cx16
%option enable_floats
floats {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
; ---- 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 floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
; 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 {{
phx
lda #<value
ldy #>value
jsr MOVFM ; load float into fac1
jsr FOUT ; fac1 to string in A/Y
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
jsr c64.CHROUT
iny
bne -
+ plx
rts
}}
}
%asminclude "library:c64/floats.asm", ""
}

View File

@ -0,0 +1,156 @@
%target cx16
%import syslib
; bitmap pixel graphics module for the CommanderX16
; wraps the graphics functions that are in ROM.
; only black/white monchrome 320x200 for now.
graphics {
const uword WIDTH = 320
const ubyte HEIGHT = 200
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
void cx16.screen_set_mode($80)
cx16.r0 = 0
cx16.GRAPH_init()
clear_screen(1, 0)
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor)
cx16.GRAPH_clear()
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
cx16.r0 = x1
cx16.r1 = y1
cx16.r2 = x2
cx16.r3 = y2
cx16.GRAPH_draw_line()
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
;cx16.r0 = xcenter - radius/2
;cx16.r1 = ycenter - radius/2
;cx16.r2 = radius*2
;cx16.r3 = radius*2
;cx16.GRAPH_draw_oval(false) ; TODO currently is not implemented on cx16, does a BRK
; Midpoint algorithm
ubyte @zp xx = radius
ubyte @zp yy = 0
byte @zp decisionOver2 = 1-xx as byte
while xx>=yy {
cx16.r0 = xcenter + xx
cx16.r1 = ycenter + yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter + xx
cx16.r1 = ycenter - yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter + xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter - xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
yy++
if decisionOver2<=0 {
decisionOver2 += 2*yy+1
} else {
xx--
decisionOver2 += 2*(yy-xx)+1
}
}
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; cx16.r0 = xcenter - radius/2
; cx16.r1 = ycenter - radius/2
; cx16.r2 = radius*2
; cx16.r3 = radius*2
; cx16.GRAPH_draw_oval(true) ; TODO currently is not implemented on cx16, does a BRK
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
while xx>=yy {
ubyte ycenter_plus_yy = ycenter + yy
ubyte ycenter_min_yy = ycenter - yy
ubyte ycenter_plus_xx = ycenter + xx
ubyte ycenter_min_xx = ycenter - xx
uword @zp plotx
for plotx in xcenter to xcenter+xx {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
for plotx in xcenter-xx to xcenter-1 {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
for plotx in xcenter to xcenter+yy {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
for plotx in xcenter-yy to xcenter {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
}
}
}
sub plot(uword plotx, ubyte ploty) {
cx16.r0 = plotx
cx16.r1 = ploty
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
}

View File

@ -0,0 +1,291 @@
; 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
%target cx16
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) -> ubyte @A, uword @ XY ; read/set top of memory pointer, returns number of banks in A
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
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 {
; 65c02 hardware vectors:
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; the sixteen virtual 16-bit registers
&uword r0 = $0002
&uword r1 = $0004
&uword r2 = $0006
&uword r3 = $0008
&uword r4 = $000a
&uword r5 = $000c
&uword r6 = $000e
&uword r7 = $0010
&uword r8 = $0012
&uword r9 = $0014
&uword r10 = $0016
&uword r11 = $0018
&uword r12 = $001a
&uword r13 = $001c
&uword r14 = $001e
&uword r15 = $0020
; VERA registers
const uword VERA_BASE = $9F20
&ubyte VERA_ADDR_L = VERA_BASE + $0000
&ubyte VERA_ADDR_M = VERA_BASE + $0001
&ubyte VERA_ADDR_H = VERA_BASE + $0002
&ubyte VERA_DATA0 = VERA_BASE + $0003
&ubyte VERA_DATA1 = VERA_BASE + $0004
&ubyte VERA_CTRL = VERA_BASE + $0005
&ubyte VERA_IEN = VERA_BASE + $0006
&ubyte VERA_ISR = VERA_BASE + $0007
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
; VERA_PSG_BASE = $1F9C0
; VERA_PALETTE_BASE = $1FA00
; VERA_SPRITES_BASE = $1FC00
; I/O
const uword via1 = $9f60 ;VIA 6522 #1
&ubyte d1prb = via1+0
&ubyte d1pra = via1+1
&ubyte d1ddrb = via1+2
&ubyte d1ddra = via1+3
&ubyte d1t1l = via1+4
&ubyte d1t1h = via1+5
&ubyte d1t1ll = via1+6
&ubyte d1t1lh = via1+7
&ubyte d1t2l = via1+8
&ubyte d1t2h = via1+9
&ubyte d1sr = via1+10
&ubyte d1acr = via1+11
&ubyte d1pcr = via1+12
&ubyte d1ifr = via1+13
&ubyte d1ier = via1+14
&ubyte d1ora = via1+15
const uword via2 = $9f70 ;VIA 6522 #2
&ubyte d2prb =via2+0
&ubyte d2pra =via2+1
&ubyte d2ddrb =via2+2
&ubyte d2ddra =via2+3
&ubyte d2t1l =via2+4
&ubyte d2t1h =via2+5
&ubyte d2t1ll =via2+6
&ubyte d2t1lh =via2+7
&ubyte d2t2l =via2+8
&ubyte d2t2h =via2+9
&ubyte d2sr =via2+10
&ubyte d2acr =via2+11
&ubyte d2pcr =via2+12
&ubyte d2ifr =via2+13
&ubyte d2ier =via2+14
&ubyte d2ora =via2+15
; ---- Commander X-16 additions on top of C64 kernal routines ----
; spelling of the names is taken from the Commander X-16 rom sources
; supported C128 additions
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
romsub $ff5f = screen_set_mode(ubyte mode @A) clobbers(A, X, Y) -> ubyte @Pc
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
romsub $ff6e = jsrfar()
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7d = primm()
; X16 additions
romsub $ff44 = macptr() clobbers(A,X,Y)
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
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, X, Y) ; outout args: r0, r1, r2, r3L
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
; high level graphics & fonts
romsub $ff20 = GRAPH_init() clobbers(A,X,Y) ; uses vectors=r0
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
romsub $ff26 = GRAPH_set_window() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
romsub $ff2c = GRAPH_draw_line() clobbers(A,X,Y) ; uses x1=r0, y1=r1, x2=r2, y2=r3
romsub $ff2f = GRAPH_draw_rect(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3, cornerradius=r4
romsub $ff32 = GRAPH_move_rect() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, width=r4, height=r5
romsub $ff35 = GRAPH_draw_oval(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $ff38 = GRAPH_draw_image() clobbers(A,X,Y) ; uses x=r0, y=r1, ptr=r2, width=r3, height=r4
romsub $ff3b = GRAPH_set_font() clobbers(A,X,Y) ; uses ptr=r0
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc) clobbers(A,X,Y)
romsub $ff41 = GRAPH_put_char(ubyte char @A) clobbers(A,X,Y) ; uses x=r0, y=r1
; framebuffer
romsub $fef6 = FB_init() clobbers(A,X,Y)
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A ; also outputs width=r0, height=r1
romsub $fefc = FB_set_palette(ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y) ; also uses pointer=r0
romsub $feff = FB_cursor_position() clobbers(A,X,Y) ; uses x=r0, y=r1
romsub $ff02 = FB_cursor_next_line() clobbers(A,X,Y) ; uses x=r0
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
romsub $ff08 = FB_get_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
romsub $ff0e = FB_set_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y) ; also uses mask=r0L
romsub $ff17 = FB_fill_pixels(ubyte color @A) clobbers(A,X,Y) ; also uses count=r0, step=r1
romsub $ff1a = FB_filter_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
romsub $ff1d = FB_move_pixels() clobbers(A,X,Y) ; 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) clobbers(A,X,Y) -> ubyte @Pc ; also uses pixels=r0, mask=r1, bpp=r2L
romsub $fef3 = sprite_set_position(ubyte number @A) clobbers(A,X,Y) ; also uses x=r0 and y=r1
romsub $fee4 = memory_fill(ubyte value @A) clobbers(A,X,Y) ; uses address=r0, num_bytes=r1
romsub $fee7 = memory_copy() clobbers(A,X,Y) ; uses source=r0, target=r1, num_bytes=r2
romsub $feea = memory_crc() clobbers(A,X,Y) ; uses address=r0, num_bytes=r1 result->r2
romsub $feed = memory_decompress() clobbers(A,X,Y) ; uses input=r0, output=r1 result->r1
romsub $fedb = console_init() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc) clobbers(A,X,Y)
romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
romsub $fed8 = console_put_image() clobbers(A,X,Y) ; uses ptr=r0, width=r1, height=r2
romsub $fed5 = console_set_paging_message() clobbers(A,X,Y) ; uses messageptr=r0
romsub $fed2 = kbdbuf_put(ubyte key @A) clobbers(A,X,Y)
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
romsub $fecc = monitor() clobbers(A,X,Y)
; ---- 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
;stz d1prb ; select rom bank 0
lda #$80
sta VERA_CTRL
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
lda #$90 ; black
jsr c64.CHROUT
lda #1 ; swap fg/bg
jsr c64.CHROUT
lda #$9e ; yellow
jsr c64.CHROUT
lda #147 ; clear screen
jsr c64.CHROUT
lda #0
tax
tay
clc
clv
cli
rts
}}
}
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
%asm {{
sei
lda #14
sta $01
stz cx16.d1prb ; bank the kernal in
jmp (cx16.RESET_VEC)
}}
}
}

View File

@ -0,0 +1,696 @@
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
%target cx16
%import syslib
%import conv
txt {
const ubyte DEFAULT_WIDTH = 80
const ubyte DEFAULT_HEIGHT = 60
sub clear_screen() {
clear_screenchars(' ')
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
%asm {{
sty _ly+1
phx
pha
jsr c64.SCREEN ; get dimensions in X/Y
txa
lsr a
lsr a
sta _lx+1
stz cx16.VERA_CTRL
lda #%00010000
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
stz cx16.VERA_ADDR_L ; start at (0,0)
stz cx16.VERA_ADDR_M
pla
_lx ldx #0 ; modified
phy
_ly ldy #1 ; modified
- sta cx16.VERA_DATA0
sty cx16.VERA_DATA0
sta cx16.VERA_DATA0
sty cx16.VERA_DATA0
sta cx16.VERA_DATA0
sty cx16.VERA_DATA0
sta cx16.VERA_DATA0
sty cx16.VERA_DATA0
dex
bne -
ply
dey
beq +
stz cx16.VERA_ADDR_L
inc cx16.VERA_ADDR_M ; next line
bra _lx
+ plx
rts
}}
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
phx
pha
jsr c64.SCREEN ; get dimensions in X/Y
txa
lsr a
lsr a
sta _lx+1
stz cx16.VERA_CTRL
lda #%00100000
sta cx16.VERA_ADDR_H ; enable auto increment by 2, bank 0.
stz cx16.VERA_ADDR_L ; start at (0,0)
stz cx16.VERA_ADDR_M
pla
_lx ldx #0 ; modified
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
dex
bne -
dey
beq +
stz cx16.VERA_ADDR_L
inc cx16.VERA_ADDR_M ; next line
bra _lx
+ plx
rts
}}
}
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
; ---- clear the character screen colors with the given color (leaves characters).
; (assumes color matrix is at the default address)
%asm {{
phx
sta _la+1
jsr c64.SCREEN ; get dimensions in X/Y
txa
lsr a
lsr a
sta _lx+1
stz cx16.VERA_CTRL
lda #%00100000
sta cx16.VERA_ADDR_H ; enable auto increment by 2, bank 0.
lda #1
sta cx16.VERA_ADDR_L ; start at (1,0)
stz cx16.VERA_ADDR_M
_lx ldx #0 ; modified
_la lda #0 ; modified
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
dex
bne -
dey
beq +
lda #1
sta cx16.VERA_ADDR_L
inc cx16.VERA_ADDR_M ; next line
bra _lx
+ plx
rts
}}
}
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
sub color (ubyte txtcol) {
c64.CHROUT(color_to_charcode[txtcol & 15])
}
sub color2 (ubyte txtcol, ubyte bgcol) {
c64.CHROUT(color_to_charcode[bgcol & 15])
c64.CHROUT(1) ; switch fg and bg colors
c64.CHROUT(color_to_charcode[txtcol & 15])
}
sub lowercase() {
cx16.screen_set_charset(3, 0) ; lowercase charset
}
sub uppercase() {
cx16.screen_set_charset(2, 0) ; uppercase charset
}
asmsub scroll_left (ubyte dummy @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
dex
stx _lx+1
dey
sty P8ZP_SCRATCH_B1 ; number of rows to scroll
_nextline
stz cx16.VERA_CTRL ; data port 0: source column
lda #%00010000 ; auto increment 1
sta cx16.VERA_ADDR_H
lda #2
sta cx16.VERA_ADDR_L ; begin in column 1
ldy P8ZP_SCRATCH_B1
sty cx16.VERA_ADDR_M
lda #1
sta cx16.VERA_CTRL ; data port 1: destination column
lda #%00010000 ; auto increment 1
sta cx16.VERA_ADDR_H
stz cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
_lx ldx #0 ; modified
- lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy char
lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy color
dex
bne -
dec P8ZP_SCRATCH_B1
bpl _nextline
lda #0
sta cx16.VERA_CTRL
plx
rts
}}
}
asmsub scroll_right (ubyte dummy @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
dex
stx _lx+1
txa
asl a
dea
sta _rcol+1
ina
ina
sta _rcol2+1
dey
sty P8ZP_SCRATCH_B1 ; number of rows to scroll
_nextline
stz cx16.VERA_CTRL ; data port 0: source column
lda #%00011000 ; auto decrement 1
sta cx16.VERA_ADDR_H
_rcol lda #79*2-1 ; modified
sta cx16.VERA_ADDR_L ; begin in rightmost column minus one
ldy P8ZP_SCRATCH_B1
sty cx16.VERA_ADDR_M
lda #1
sta cx16.VERA_CTRL ; data port 1: destination column
lda #%00011000 ; auto decrement 1
sta cx16.VERA_ADDR_H
_rcol2 lda #79*2+1 ; modified
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
_lx ldx #0 ; modified
- lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy char
lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy color
dex
bne -
dec P8ZP_SCRATCH_B1
bpl _nextline
lda #0
sta cx16.VERA_CTRL
plx
rts
}}
}
asmsub scroll_up (ubyte dummy @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
stx _nextline+1
dey
sty P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL ; data port 0 is source
lda #1
sta cx16.VERA_ADDR_M ; start at second line
stz cx16.VERA_ADDR_L
lda #%00010000
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
lda #1
sta cx16.VERA_CTRL ; data port 1 is destination
stz cx16.VERA_ADDR_M ; start at top line
stz cx16.VERA_ADDR_L
lda #%00010000
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
_nextline
ldx #80 ; modified
- lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy char
lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy color
dex
bne -
dec P8ZP_SCRATCH_B1
beq +
stz cx16.VERA_CTRL ; data port 0
stz cx16.VERA_ADDR_L
inc cx16.VERA_ADDR_M
lda #1
sta cx16.VERA_CTRL ; data port 1
stz cx16.VERA_ADDR_L
inc cx16.VERA_ADDR_M
bra _nextline
+ lda #0
sta cx16.VERA_CTRL
plx
rts
}}
}
asmsub scroll_down (ubyte dummy @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
stx _nextline+1
dey
sty P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL ; data port 0 is source
dey
sty cx16.VERA_ADDR_M ; start at line before bottom line
stz cx16.VERA_ADDR_L
lda #%00010000
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
lda #1
sta cx16.VERA_CTRL ; data port 1 is destination
iny
sty cx16.VERA_ADDR_M ; start at bottom line
stz cx16.VERA_ADDR_L
lda #%00010000
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
_nextline
ldx #80 ; modified
- lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy char
lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy color
dex
bne -
dec P8ZP_SCRATCH_B1
beq +
stz cx16.VERA_CTRL ; data port 0
stz cx16.VERA_ADDR_L
dec cx16.VERA_ADDR_M
lda #1
sta cx16.VERA_CTRL ; data port 1
stz cx16.VERA_ADDR_L
dec cx16.VERA_ADDR_M
bra _nextline
+ lda #0
sta cx16.VERA_CTRL
plx
rts
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to c64.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr c64.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
phx
jsr conv.ubyte2decimal
pha
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
txa
jsr c64.CHROUT
plx
rts
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
phx
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr c64.CHROUT
_ones txa
jsr c64.CHROUT
plx
rts
}}
}
asmsub print_b (byte value @ A) clobbers(A,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
phx
pha
cmp #0
bpl +
lda #'-'
jsr c64.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
phx
bcc +
pha
lda #'$'
jsr c64.CHROUT
pla
+ jsr conv.ubyte2hex
jsr c64.CHROUT
tya
jsr c64.CHROUT
plx
rts
}}
}
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
phx
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr c64.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr c64.CHROUT
dey
bne -
plx
rts
}}
}
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
phx
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr c64.CHROUT
iny
bne -
+ plx
rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
phx
jsr conv.uword2decimal
plx
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr c64.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp c64.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr c64.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr c64.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
; ---- sets the character in the screen matrix at the given position
%asm {{
pha
txa
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
}}
}
asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
%asm {{
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
}}
}
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
; ---- set the color in A on the screen matrix at the given position
%asm {{
pha
txa
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
}}
}
asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- get the color in the screen color matrix at the given location
%asm {{
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
phx
lda column
asl a
tax
ldy row
lda charcolor
and #$0f
sta P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
and #$f0
ora P8ZP_SCRATCH_B1
sta cx16.VERA_DATA0
plx
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
%asm {{
phx
tax
clc
jsr c64.PLOT
plx
rts
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
; -- returns the text screen width (number of columns)
%asm {{
jsr c64.SCREEN
txa
rts
}}
}
asmsub height() clobbers(X, Y) -> ubyte @A {
; -- returns the text screen height (number of rows)
%asm {{
jsr c64.SCREEN
tya
rts
}}
}
}

View File

@ -1,201 +0,0 @@
; 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
&ubyte r0 = $02
&ubyte r0L = $02
&ubyte r0H = $03
&ubyte r1 = $04
&ubyte r1L = $04
&ubyte r1H = $05
&ubyte r2 = $06
&ubyte r2L = $06
&ubyte r2H = $07
&ubyte r3 = $08
&ubyte r3L = $08
&ubyte r3H = $09
&ubyte r4 = $0a
&ubyte r4L = $0a
&ubyte r4H = $0b
&ubyte r5 = $0c
&ubyte r5L = $0c
&ubyte r5H = $0d
&ubyte r6 = $0e
&ubyte r6L = $0e
&ubyte r6H = $0f
&ubyte r7 = $10
&ubyte r7L = $10
&ubyte r7H = $11
&ubyte r8 = $12
&ubyte r8L = $12
&ubyte r8H = $13
&ubyte r9 = $14
&ubyte r9L = $14
&ubyte r9H = $15
&ubyte r10 = $16
&ubyte r10L = $16
&ubyte r10H = $17
&ubyte r11 = $18
&ubyte r11L = $18
&ubyte r11H = $19
&ubyte r12 = $1a
&ubyte r12L = $1a
&ubyte r12H = $1b
&ubyte r13 = $1c
&ubyte r13L = $1c
&ubyte r13H = $1d
&ubyte r14 = $1e
&ubyte r14L = $1e
&ubyte r14H = $1f
&ubyte r15 = $20
&ubyte r15L = $20
&ubyte r15H = $21
; TODO subroutine args + soubroutine returnvalues + clobber registers
; supported C128 additions
romsub $ff4a = close_all()
romsub $ff59 = lkupla()
romsub $ff5c = lkupsa()
romsub $ff5f = screen_set_mode()
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()
romsub $ff68 = mouse_config()
romsub $ff6b = mouse_get()
romsub $ff71 = mouse_scan()
romsub $ff53 = joystick_scan()
romsub $ff56 = joystick_get()
romsub $ff4d = clock_set_date_time()
romsub $ff50 = clock_get_date_time()
; high level graphics & fonts
romsub $ff20 = GRAPH_init()
romsub $ff23 = GRAPH_clear()
romsub $ff26 = GRAPH_set_window()
romsub $ff29 = GRAPH_set_colors()
romsub $ff2c = GRAPH_draw_line()
romsub $ff2f = GRAPH_draw_rect()
romsub $ff32 = GRAPH_move_rect()
romsub $ff35 = GRAPH_draw_oval()
romsub $ff38 = GRAPH_draw_image()
romsub $ff3b = GRAPH_set_font()
romsub $ff3e = GRAPH_get_char_size()
romsub $ff41 = GRAPH_put_char()
; TODO framebuffer API not yet included, include it
romsub $fef0 = sprite_set_image()
romsub $fef3 = sprite_set_position()
romsub $fee4 = memory_fill()
romsub $fee7 = memory_copy()
romsub $feea = memory_crc()
romsub $feed = memory_decompress()
romsub $fedb = console_init()
romsub $fede = console_put_char()
romsub $fee1 = console_get_char()
romsub $fed8 = console_put_image()
romsub $fed5 = console_set_paging_message()
romsub $fed2 = kbdbuf_put()
romsub $fecf = entropy_get()
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
lda #0
sta $00
sta $01
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
lda #0
tax
tay
clc
clv
cli
lda #66
clc
jsr console_put_char
rts
}}
}
}

View File

@ -1,240 +0,0 @@
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
%import cx16lib
%import conv
txt {
asmsub clear_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- clear the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
%asm {{
brk ; TODO
}}
}
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 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
}}
}
}

View File

@ -11,6 +11,10 @@
; http://codebase64.org/doku.php?id=base:6502_6510_maths
;
math_store_reg .byte 0 ; temporary storage
multiply_bytes .proc
; -- multiply 2 bytes A and Y, result as byte in A (signed or unsigned)
sta P8ZP_SCRATCH_B1 ; num1
@ -27,11 +31,11 @@ _enterloop lsr P8ZP_SCRATCH_REG
.pend
multiply_bytes_16 .proc
multiply_bytes_into_word .proc
; -- multiply 2 bytes A and Y, result as word in A/Y (unsigned)
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
stx P8ZP_SCRATCH_REG_X
stx math_store_reg
lda #0
ldx #8
lsr P8ZP_SCRATCH_B1
@ -44,7 +48,7 @@ multiply_bytes_16 .proc
bne -
tay
lda P8ZP_SCRATCH_B1
ldx P8ZP_SCRATCH_REG_X
ldx math_store_reg
rts
.pend
@ -57,9 +61,9 @@ multiply_words .proc
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
mult16 lda #$00
mult16 lda #0
sta result+2 ; clear upper bits of product
sta result+3
ldx #16 ; for all 16 bits...
@ -79,20 +83,52 @@ mult16 lda #$00
ror result
dex
bne -
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
result .byte 0,0,0,0
.pend
divmod_b_asm .proc
; signed byte division: make everything positive and fix sign afterwards
sta P8ZP_SCRATCH_B1
tya
eor P8ZP_SCRATCH_B1
php ; save sign
lda P8ZP_SCRATCH_B1
bpl +
eor #$ff
sec
adc #0 ; make it positive
+ pha
tya
bpl +
eor #$ff
sec
adc #0 ; make it positive
tay
+ pla
jsr divmod_ub_asm
sta _remainder
plp
bpl +
tya
eor #$ff
sec
adc #0 ; negate result
tay
+ rts
_remainder .byte 0
.pend
divmod_ub_asm .proc
; TODO divmod_ub_asm doesn't work correctly. (remainder = ok, quotient = FAULTY)
; -- divide A by Y, result quotient in Y, remainder in A (unsigned)
; division by zero will result in quotient = 255 and remainder = original number
sty P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_B1
stx P8ZP_SCRATCH_REG_X
stx math_store_reg
lda #0
ldx #8
@ -105,10 +141,53 @@ divmod_ub_asm .proc
dex
bne -
ldy P8ZP_SCRATCH_B1
ldx P8ZP_SCRATCH_REG_X
ldx math_store_reg
rts
.pend
divmod_w_asm .proc
; signed word division: make everything positive and fix sign afterwards
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_W1+1
eor P8ZP_SCRATCH_W2+1
php ; save sign
lda P8ZP_SCRATCH_W1+1
bpl +
lda #0
sec
sbc P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
lda #0
sbc P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W1+1
+ lda P8ZP_SCRATCH_W2+1
bpl +
lda #0
sec
sbc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W2
lda #0
sbc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W2+1
+ tay
lda P8ZP_SCRATCH_W2
jsr divmod_uw_asm
plp ; restore sign
bpl +
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda #0
sec
sbc P8ZP_SCRATCH_W2
pha
lda #0
sbc P8ZP_SCRATCH_W2+1
tay
pla
+ rts
.pend
divmod_uw_asm .proc
; -- divide two unsigned words (16 bit each) into 16 bit results
; input: P8ZP_SCRATCH_W1 in ZP: 16 bit number, A/Y: 16 bit divisor
@ -122,7 +201,7 @@ result = dividend ;save memory by reusing divident to store the result
sta _divisor
sty _divisor+1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda #0 ;preset remainder to 0
sta remainder
sta remainder+1
@ -149,7 +228,7 @@ result = dividend ;save memory by reusing divident to store the result
lda result
ldy result+1
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
rts
_divisor .word 0
.pend
@ -233,7 +312,8 @@ _seed .word $2c9e
.pend
mul_byte_3 .proc
; ----------- optimized multiplications (stack) : ---------
stack_mul_byte_3 .proc
; X + X*2
lda P8ESTACK_LO+1,x
asl a
@ -243,7 +323,7 @@ mul_byte_3 .proc
rts
.pend
mul_word_3 .proc
stack_mul_word_3 .proc
; W*2 + W
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_REG
@ -260,7 +340,7 @@ mul_word_3 .proc
.pend
mul_byte_5 .proc
stack_mul_byte_5 .proc
; X*4 + X
lda P8ESTACK_LO+1,x
asl a
@ -271,7 +351,7 @@ mul_byte_5 .proc
rts
.pend
mul_word_5 .proc
stack_mul_word_5 .proc
; W*4 + W
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_REG
@ -290,7 +370,7 @@ mul_word_5 .proc
.pend
mul_byte_6 .proc
stack_mul_byte_6 .proc
; (X*2 + X)*2
lda P8ESTACK_LO+1,x
asl a
@ -301,7 +381,7 @@ mul_byte_6 .proc
rts
.pend
mul_word_6 .proc
stack_mul_word_6 .proc
; (W*2 + W)*2
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_REG
@ -319,7 +399,7 @@ mul_word_6 .proc
rts
.pend
mul_byte_7 .proc
stack_mul_byte_7 .proc
; X*8 - X
lda P8ESTACK_LO+1,x
asl a
@ -331,7 +411,7 @@ mul_byte_7 .proc
rts
.pend
mul_word_7 .proc
stack_mul_word_7 .proc
; W*8 - W
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_REG
@ -351,7 +431,7 @@ mul_word_7 .proc
rts
.pend
mul_byte_9 .proc
stack_mul_byte_9 .proc
; X*8 + X
lda P8ESTACK_LO+1,x
asl a
@ -363,7 +443,7 @@ mul_byte_9 .proc
rts
.pend
mul_word_9 .proc
stack_mul_word_9 .proc
; W*8 + W
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_REG
@ -383,7 +463,7 @@ mul_word_9 .proc
rts
.pend
mul_byte_10 .proc
stack_mul_byte_10 .proc
; (X*4 + X)*2
lda P8ESTACK_LO+1,x
asl a
@ -395,7 +475,7 @@ mul_byte_10 .proc
rts
.pend
mul_word_10 .proc
stack_mul_word_10 .proc
; (W*4 + W)*2
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_REG
@ -415,7 +495,7 @@ mul_word_10 .proc
rts
.pend
mul_byte_11 .proc
stack_mul_byte_11 .proc
; (X*2 + X)*4 - X
lda P8ESTACK_LO+1,x
asl a
@ -431,7 +511,7 @@ mul_byte_11 .proc
; mul_word_11 is skipped (too much code)
mul_byte_12 .proc
stack_mul_byte_12 .proc
; (X*2 + X)*4
lda P8ESTACK_LO+1,x
asl a
@ -443,7 +523,7 @@ mul_byte_12 .proc
rts
.pend
mul_word_12 .proc
stack_mul_word_12 .proc
; (W*2 + W)*4
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_REG
@ -463,7 +543,7 @@ mul_word_12 .proc
rts
.pend
mul_byte_13 .proc
stack_mul_byte_13 .proc
; (X*2 + X)*4 + X
lda P8ESTACK_LO+1,x
asl a
@ -479,7 +559,7 @@ mul_byte_13 .proc
; mul_word_13 is skipped (too much code)
mul_byte_14 .proc
stack_mul_byte_14 .proc
; (X*8 - X)*2
lda P8ESTACK_LO+1,x
asl a
@ -494,7 +574,7 @@ mul_byte_14 .proc
; mul_word_14 is skipped (too much code)
mul_byte_15 .proc
stack_mul_byte_15 .proc
; X*16 - X
lda P8ESTACK_LO+1,x
asl a
@ -507,7 +587,7 @@ mul_byte_15 .proc
rts
.pend
mul_word_15 .proc
stack_mul_word_15 .proc
; W*16 - W
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_REG
@ -529,7 +609,7 @@ mul_word_15 .proc
rts
.pend
mul_byte_20 .proc
stack_mul_byte_20 .proc
; (X*4 + X)*4
lda P8ESTACK_LO+1,x
asl a
@ -542,7 +622,7 @@ mul_byte_20 .proc
rts
.pend
mul_word_20 .proc
stack_mul_word_20 .proc
; (W*4 + W)*4
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_REG
@ -564,7 +644,7 @@ mul_word_20 .proc
rts
.pend
mul_byte_25 .proc
stack_mul_byte_25 .proc
; (X*2 + X)*8 + X
lda P8ESTACK_LO+1,x
asl a
@ -579,27 +659,26 @@ mul_byte_25 .proc
rts
.pend
mul_word_25 .proc
; W + W*8 + W*16
stack_mul_word_25 .proc
; W = (W*2 + W) *8 + W
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO+1,x
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W1
clc
adc P8ESTACK_LO+1,x
sta P8ESTACK_LO+1,x
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc P8ESTACK_HI+1,x
sta P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_W1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
clc
adc P8ESTACK_LO+1,x
sta P8ESTACK_LO+1,x
@ -609,21 +688,16 @@ mul_word_25 .proc
rts
.pend
mul_byte_40 .proc
; (X*4 + X)*8
stack_mul_byte_40 .proc
lda P8ESTACK_LO+1,x
asl a
asl a
clc
adc P8ESTACK_LO+1,x
asl a
asl a
asl a
and #7
tay
lda mul_byte_40._forties,y
sta P8ESTACK_LO+1,x
rts
.pend
mul_word_40 .proc
stack_mul_word_40 .proc
; (W*4 + W)*8
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_REG
@ -647,6 +721,529 @@ mul_word_40 .proc
rts
.pend
stack_mul_byte_50 .proc
lda P8ESTACK_LO+1,x
and #7
tay
lda mul_byte_50._fifties, y
sta P8ESTACK_LO+1,x
rts
.pend
stack_mul_word_50 .proc
; W = W * 25 * 2
jsr stack_mul_word_25
asl P8ESTACK_LO+1,x
rol P8ESTACK_HI+1,x
rts
.pend
stack_mul_byte_80 .proc
lda P8ESTACK_LO+1,x
and #3
tay
lda mul_byte_80._eighties, y
sta P8ESTACK_LO+1,x
rts
.pend
stack_mul_word_80 .proc
; W = W * 40 * 2
jsr stack_mul_word_40
asl P8ESTACK_LO+1,x
rol P8ESTACK_HI+1,x
rts
.pend
stack_mul_byte_100 .proc
lda P8ESTACK_LO+1,x
and #3
tay
lda mul_byte_100._hundreds, y
sta P8ESTACK_LO+1,x
rts
.pend
stack_mul_word_100 .proc
; W = W * 25 * 4
jsr stack_mul_word_25
asl P8ESTACK_LO+1,x
rol P8ESTACK_HI+1,x
asl P8ESTACK_LO+1,x
rol P8ESTACK_HI+1,x
rts
.pend
; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : ---------
mul_byte_3 .proc
; A = A + A*2
sta P8ZP_SCRATCH_REG
asl a
clc
adc P8P_P8ZP_SCRATCH_REG
rts
.pend
mul_word_3 .proc
; AY = AY*2 + AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
asl a
rol P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc P8ZP_SCRATCH_W2+1
tay
lda P8ZP_SCRATCH_W1
rts
.pend
mul_byte_5 .proc
; A = A*4 + A
sta P8ZP_SCRATCH_REG
asl a
asl a
clc
adc P8ZP_SCRATCH_REG
rts
.pend
mul_word_5 .proc
; AY = AY*4 + AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc P8ZP_SCRATCH_W2+1
tay
lda P8ZP_SCRATCH_W1
rts
.pend
mul_byte_6 .proc
; A = (A*2 + A)*2
sta P8ZP_SCRATCH_REG
asl a
clc
adc P8ZP_SCRATCH_REG
asl a
rts
.pend
mul_word_6 .proc
; AY = (AY*2 + AY)*2
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
asl a
rol P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
tay
lda P8ZP_SCRATCH_W1+1
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W1+1
tya
asl a
rol P8ZP_SCRATCH_W1+1
ldy P8ZP_SCRATCH_W1+1
rts
.pend
mul_byte_7 .proc
; A = A*8 - A
sta P8ZP_SCRATCH_REG
asl a
asl a
asl a
sec
sbc P8ZP_SCRATCH_REG
rts
.pend
mul_word_7 .proc
; AY = AY*8 - AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
sec
sbc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
sbc P8ZP_SCRATCH_W2+1
tay
lda P8ZP_SCRATCH_W1
rts
.pend
mul_byte_9 .proc
; A = A*8 + A
sta P8ZP_SCRATCH_REG
asl a
asl a
asl a
clc
adc P8ZP_SCRATCH_REG
rts
.pend
mul_word_9 .proc
; AY = AY*8 + AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc P8ZP_SCRATCH_W2+1
tay
lda P8ZP_SCRATCH_W1
rts
rts
.pend
mul_byte_10 .proc
; A=(A*4 + A)*2
sta P8ZP_SCRATCH_REG
asl a
asl a
clc
adc P8ZP_SCRATCH_REG
asl a
rts
.pend
mul_word_10 .proc
; AY=(AY*4 + AY)*2
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_W1
asl a
rol P8ZP_SCRATCH_W1+1
ldy P8ZP_SCRATCH_W1+1
rts
.pend
mul_byte_11 .proc
; A=(A*2 + A)*4 - A
sta P8ZP_SCRATCH_REG
asl a
clc
adc P8ZP_SCRATCH_REG
asl a
asl a
sec
sbc P8ZP_SCRATCH_REG
rts
.pend
; mul_word_11 is skipped (too much code)
mul_byte_12 .proc
; A=(A*2 + A)*4
sta P8ZP_SCRATCH_REG
asl a
clc
adc P8ZP_SCRATCH_REG
asl a
asl a
rts
.pend
mul_word_12 .proc
; AY=(AY*2 + AY)*4
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
asl a
rol P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_W1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
ldy P8ZP_SCRATCH_W1+1
rts
.pend
mul_byte_13 .proc
; A=(A*2 + A)*4 + A
sta P8ZP_SCRATCH_REG
asl a
clc
adc P8ZP_SCRATCH_REG
asl a
asl a
clc
adc P8ZP_SCRATCH_REG
rts
.pend
; mul_word_13 is skipped (too much code)
mul_byte_14 .proc
; A=(A*8 - A)*2
sta P8ZP_SCRATCH_REG
asl a
asl a
asl a
sec
sbc P8ZP_SCRATCH_REG
asl a
rts
.pend
; mul_word_14 is skipped (too much code)
mul_byte_15 .proc
; A=A*16 - A
sta P8ZP_SCRATCH_REG
asl a
asl a
asl a
asl a
sec
sbc P8ZP_SCRATCH_REG
rts
.pend
mul_word_15 .proc
; AY = AY * 16 - AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
sec
sbc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
sbc P8ZP_SCRATCH_W2+1
tay
lda P8ZP_SCRATCH_W1
rts
.pend
mul_byte_20 .proc
; A=(A*4 + A)*4
sta P8ZP_SCRATCH_REG
asl a
asl a
clc
adc P8ZP_SCRATCH_REG
asl a
asl a
rts
.pend
mul_word_20 .proc
; AY = AY * 10 * 2
jsr mul_word_10
sty P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
ldy P8ZP_SCRATCH_REG
rts
.pend
mul_byte_25 .proc
; A=(A*2 + A)*8 + A
sta P8ZP_SCRATCH_REG
asl a
clc
adc P8ZP_SCRATCH_REG
asl a
asl a
asl a
clc
adc P8ZP_SCRATCH_REG
rts
.pend
mul_word_25 .proc
; AY = (AY*2 + AY) *8 + AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
asl a
rol P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_W1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc P8ZP_SCRATCH_W2+1
tay
lda P8ZP_SCRATCH_W1
rts
.pend
mul_byte_40 .proc
and #7
tay
lda _forties,y
rts
_forties .byte 0*40, 1*40, 2*40, 3*40, 4*40, 5*40, 6*40, 7*40 & 255
.pend
mul_word_40 .proc
; AY = (AY*4 + AY)*8
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
asl a
rol P8ZP_SCRATCH_W1+1
asl a
rol P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
adc P8ZP_SCRATCH_W2+1
asl P8ZP_SCRATCH_W1
rol a
asl P8ZP_SCRATCH_W1
rol a
asl P8ZP_SCRATCH_W1
rol a
asl P8ZP_SCRATCH_W1
rol a
tay
lda P8ZP_SCRATCH_W1
rts
.pend
mul_byte_50 .proc
and #7
tay
lda _fifties, y
rts
_fifties .byte 0*50, 1*50, 2*50, 3*50, 4*50, 5*50, 6*50 & 255, 7*50 & 255
.pend
mul_word_50 .proc
; AY = AY * 25 * 2
jsr mul_word_25
sty P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
ldy P8ZP_SCRATCH_REG
rts
.pend
mul_byte_80 .proc
and #3
tay
lda _eighties, y
rts
_eighties .byte 0*80, 1*80, 2*80, 3*80
.pend
mul_word_80 .proc
; AY = AY * 40 * 2
jsr mul_word_40
sty P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
ldy P8ZP_SCRATCH_REG
rts
.pend
mul_byte_100 .proc
and #3
tay
lda _hundreds, y
rts
_hundreds .byte 0*100, 1*100, 2*100, 3*100 & 255
.pend
mul_word_100 .proc
; AY = AY * 25 * 4
jsr mul_word_25
sty P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
ldy P8ZP_SCRATCH_REG
rts
.pend
; ----------- end optimized multiplications -----------
sign_b .proc
lda P8ESTACK_LO+1,x
beq _sign_zero

View File

@ -686,7 +686,7 @@ func_sqrt16 .proc
sta P8ZP_SCRATCH_W2
lda P8ESTACK_HI+1,x
sta P8ZP_SCRATCH_W2+1
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
ldy #$00 ; r = 0
ldx #$07
clc ; clear bit 16 of m
@ -721,7 +721,7 @@ _skip1
_skip2
iny ; r = r or d (d is 1 here)
_skip3
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
tya
sta P8ESTACK_LO+1,x
lda #0
@ -960,7 +960,7 @@ _result_maxuw .word 0
.pend
func_max_w .proc
lda #$00
lda #0
sta _result_maxw
lda #$80
sta _result_maxw+1
@ -1216,7 +1216,7 @@ func_rndw .proc
func_memcopy .proc
; note: clobbers A,Y
inx
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda P8ESTACK_LO+2,x
sta P8ZP_SCRATCH_W1
lda P8ESTACK_HI+2,x
@ -1233,7 +1233,7 @@ func_memcopy .proc
iny
dex
bne -
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
inx
inx
rts
@ -1242,7 +1242,7 @@ func_memcopy .proc
func_memset .proc
; note: clobbers A,Y
inx
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda P8ESTACK_LO+2,x
sta P8ZP_SCRATCH_W1
lda P8ESTACK_HI+2,x
@ -1253,7 +1253,7 @@ func_memset .proc
lda P8ESTACK_LO,x
ldx P8ZP_SCRATCH_B1
jsr memset
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
inx
inx
rts
@ -1264,7 +1264,7 @@ func_memsetw .proc
; -- fill memory from (SCRATCH_ZPWORD1) number of words in SCRATCH_ZPWORD2, with word value in AY.
inx
stx P8ZP_SCRATCH_REG_X
stx P8ZP_SCRATCH_REG
lda P8ESTACK_LO+2,x
sta P8ZP_SCRATCH_W1
lda P8ESTACK_HI+2,x
@ -1276,7 +1276,7 @@ func_memsetw .proc
lda P8ESTACK_LO,x
ldy P8ESTACK_HI,x
jsr memsetw
ldx P8ZP_SCRATCH_REG_X
ldx P8ZP_SCRATCH_REG
inx
inx
rts
@ -1328,9 +1328,9 @@ memset .proc
; -- fill memory from (SCRATCH_ZPWORD1), length XY, with value in A.
; clobbers X, Y
stx P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
sty _save_reg
ldy #0
ldx P8ZP_SCRATCH_REG
ldx _save_reg
beq _lastpage
_fullpage sta (P8ZP_SCRATCH_W1),y
@ -1347,6 +1347,7 @@ _lastpage ldy P8ZP_SCRATCH_B1
bne -
+ rts
_save_reg .byte 0
.pend

View File

@ -1 +1 @@
4.0
4.4

View File

@ -4,13 +4,10 @@ import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
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
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
@ -36,12 +33,13 @@ 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 'c64' and 'cx16' available", "c64")
val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
try {
@ -50,39 +48,6 @@ private fun compileMain(args: Array<String>) {
exitProcess(1)
}
when(compilationTarget) {
"c64" -> {
with(CompilationTarget) {
name = "Commodore-64"
machine = C64MachineDefinition
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
}
}
"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. Available are: c64, cx16")
exitProcess(1)
}
}
val outputPath = pathFrom(outputDir)
if(!outputPath.toFile().isDirectory) {
System.err.println("Output path doesn't exist")
@ -97,7 +62,7 @@ private fun compileMain(args: Array<String>) {
println("Continuous watch mode active. Main module: $filepath")
try {
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) {
print(" ")
@ -122,7 +87,7 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {
@ -135,7 +100,7 @@ private fun compileMain(args: Array<String>) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else if(startEmulator) {
CompilationTarget.machine.launchEmulator(compilationResult.programName)
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
}
}
}

View File

@ -53,12 +53,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(expr: BinaryExpression) {
output("(")
expr.left.accept(this)
if(expr.operator.any { it.isLetter() })
output(" ${expr.operator} ")
else
output(expr.operator)
expr.right.accept(this)
output(")")
}
override fun visit(directive: Directive) {
@ -85,7 +87,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
DataType.ARRAY_W -> "word["
DataType.ARRAY_F -> "float["
DataType.STRUCT -> "" // the name of the struct is enough
else -> "?????2"
else -> "?????"
}
}
@ -113,7 +115,10 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
VarDeclType.CONST -> output("const ")
VarDeclType.MEMORY -> output("&")
}
output(decl.struct?.name ?: "")
if(decl.datatype==DataType.STRUCT && decl.struct!=null)
output(decl.struct!!.name)
output(datatypeString(decl.datatype))
if(decl.arraysize!=null) {
decl.arraysize!!.index.accept(this)
@ -140,7 +145,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
param.second.stack -> "stack"
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
param.second.statusflag!=null -> param.second.statusflag.toString()
else -> "?????1"
else -> "?????"
}
output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
if(param.first!==subroutine.parameters.last())
@ -338,7 +343,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output("do ")
untilLoop.body.accept(this)
output(" until ")
untilLoop.untilCondition.accept(this)
untilLoop.condition.accept(this)
}
override fun visit(returnStmt: Return) {

View File

@ -137,7 +137,15 @@ interface INameScope {
}
return null
} else {
// unqualified name, find the scope the localContext is in, look in that first
// unqualified name
// special case: the do....until statement can also look INSIDE the anonymous scope
if(localContext.parent.parent is UntilLoop) {
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.getLabelOrVariable(scopedName[0])
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope()
@ -232,7 +240,7 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
override fun linkParents(parent: Node) {
modules.forEach {
it.linkParents(this)
it.linkParents(namespace)
}
}
@ -312,6 +320,14 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
}
}
}
// special case: the do....until statement can also look INSIDE the anonymous scope
if(localContext.parent.parent is UntilLoop) {
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.lookup(scopedName, localContext)
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// lookup something from the module.
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt

View File

@ -30,7 +30,7 @@ private fun ParserRuleContext.toPosition() : Position {
val customTokensource = this.start.tokenSource as? CustomLexer
val filename =
when {
customTokensource!=null -> customTokensource.modulePath.fileName.toString()
customTokensource!=null -> customTokensource.modulePath.toString()
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
else -> File(start.inputStream.sourceName).name
}
@ -254,7 +254,7 @@ private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
val normalReturntypes = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, false) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
}
@ -263,7 +263,7 @@ private class AsmSubroutineParameter(name: String,
type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
val stack: Boolean,
// TODO implement: val stack: Boolean,
position: Position) : SubroutineParameter(name, type, position)
private class AsmSubroutineReturn(val type: DataType,
@ -305,8 +305,7 @@ private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParamete
else -> throw FatalAstException("invalid register or status flag '$name'")
}
}
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister,
!it.stack?.text.isNullOrEmpty(), toPosition())
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
}
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
@ -398,7 +397,7 @@ private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
}
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
fun makeLiteral(text: String, radix: Int): NumericLiteral {
val integer: Int
var datatype = DataType.UBYTE
when (radix) {
@ -436,14 +435,14 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
}
else -> throw FatalAstException("invalid radix")
}
return NumericLiteral(integer, if (forceWord) DataType.UWORD else datatype)
return NumericLiteral(integer, datatype)
}
val terminal: TerminalNode = children[0] as TerminalNode
val integerPart = this.intpart.text
return when (terminal.symbol.type) {
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null)
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null)
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null)
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10)
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
else -> throw FatalAstException(terminal.text)
}
}
@ -472,7 +471,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
litval.charliteral()!=null -> {
try {
val cc=litval.charliteral()
NumericLiteralValue(DataType.UBYTE, CompilationTarget.encodeString(
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
} catch (ce: CharConversionException) {

View File

@ -56,8 +56,8 @@ enum class DataType {
return when(this) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
FLOAT -> CompilationTarget.machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> CompilationTarget.machine.POINTER_MEM_SIZE
FLOAT -> CompilationTarget.instance.machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> CompilationTarget.instance.machine.POINTER_MEM_SIZE
else -> -9999999
}
}
@ -165,6 +165,7 @@ object ParentSentinel : Node {
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
fun toClickableStr(): String = "$file:$line:$startCol:"
companion object {
val DUMMY = Position("<dummy>", 0, 0, 0)

View File

@ -24,7 +24,7 @@ class ErrorReporter {
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
}
val msg = "${it.position} ${it.severity} ${it.message}".trim()
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
if(msg !in alreadyReportedMessages) {
System.err.println(msg)
alreadyReportedMessages.add(msg)

View File

@ -7,11 +7,11 @@ open class FatalAstException (override var message: String) : Exception(message)
open class AstException (override var message: String) : Exception(message)
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Syntax error: $message"
override fun toString() = "${position.toClickableStr()} Syntax error: $message"
}
class ExpressionError(message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Error: $message"
override fun toString() = "${position.toClickableStr()} Error: $message"
}
class UndefinedSymbolError(symbol: IdentifierReference)

View File

@ -17,13 +17,13 @@ import kotlin.math.abs
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>")
sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
abstract fun referencesIdentifiers(vararg name: String): Boolean
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
abstract fun inferType(program: Program): InferredTypes.InferredType
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
@ -85,7 +85,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val inferred = expression.inferType(program)
return when(operator) {
@ -142,7 +142,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
override fun referencesIdentifier(vararg scopedName: String) = left.referencesIdentifier(*scopedName) || right.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program)
val rightDt = right.inferType(program)
@ -255,7 +255,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
override fun referencesIdentifier(vararg scopedName: String) = identifier.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val target = identifier.targetStatement(program.namespace)
@ -291,7 +291,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? {
val cv = expression.constValue(program) ?: return null
@ -322,7 +322,7 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifiers(vararg name: String) = false
override fun referencesIdentifier(vararg scopedName: String) = false
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -345,7 +345,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = false
override fun referencesIdentifier(vararg scopedName: String) = false
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
override fun constValue(program: Program): NumericLiteralValue? = null
@ -398,7 +398,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
throw FatalAstException("can't replace here")
}
override fun referencesIdentifiers(vararg name: String) = false
override fun referencesIdentifier(vararg scopedName: String) = false
override fun constValue(program: Program) = this
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
@ -498,7 +498,7 @@ class StringLiteralValue(val value: String,
throw FatalAstException("can't replace here")
}
override fun referencesIdentifiers(vararg name: String) = false
override fun referencesIdentifier(vararg scopedName: String) = false
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -533,7 +533,7 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
replacement.parent = this
}
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
override fun referencesIdentifier(vararg scopedName: String) = value.any { it.referencesIdentifier(*scopedName) }
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -569,10 +569,17 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when {
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
DataType.STR in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
DataType.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB)
DataType.ARRAY_UW in dts ||
DataType.ARRAY_W in dts ||
DataType.ARRAY_UB in dts ||
DataType.ARRAY_B in dts ||
DataType.ARRAY_F in dts ||
DataType.STRUCT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
else -> InferredTypes.InferredType.unknown()
}
}
@ -631,7 +638,7 @@ class RangeExpr(var from: Expression,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
override fun referencesIdentifier(vararg scopedName: String): Boolean = from.referencesIdentifier(*scopedName) || to.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program)
val toDt=to.inferType(program)
@ -642,7 +649,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 {
@ -664,8 +678,8 @@ class RangeExpr(var from: Expression,
val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) {
// string range -> int range over character values
fromVal = CompilationTarget.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = CompilationTarget.encodeString(toString.value, fromString.altEncoding)[0].toInt()
fromVal = CompilationTarget.instance.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = CompilationTarget.instance.encodeString(toString.value, fromString.altEncoding)[0].toInt()
} else {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
@ -737,7 +751,8 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
override fun referencesIdentifier(vararg scopedName: String): Boolean =
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
return when (val targetStmt = targetStatement(program.namespace)) {
@ -759,6 +774,18 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
else -> throw FatalAstException("requires a reference value")
}
}
fun firstStructVarName(namespace: INameScope): String? {
// take the name of the first struct member of the structvariable instead
// if it's just a regular variable, return null.
val struct = memberOfStruct(namespace) ?: return null
val decl = targetVarDecl(namespace)!!
val firstStructMember = struct.nameOfFirstMember()
// find the flattened var that belongs to this first struct member
val firstVarName = listOf(decl.name, firstStructMember)
val firstVar = definingScope().lookup(firstVarName, this) as VarDecl
return firstVar.name
}
}
class FunctionCall(override var target: IdentifierReference,
@ -825,7 +852,7 @@ class FunctionCall(override var target: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || args.any{it.referencesIdentifiers(*name)}
override fun referencesIdentifier(vararg scopedName: String): Boolean = target.referencesIdentifier(*scopedName) || args.any{it.referencesIdentifier(*scopedName)}
override fun inferType(program: Program): InferredTypes.InferredType {
val constVal = constValue(program ,false)

View File

@ -7,7 +7,9 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.CompilationOptions
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.functions.BuiltinFunctions
import java.io.File
@ -118,6 +120,11 @@ internal class AstChecker(private val program: Program,
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
errors.err("for loop requires a variable to loop with", forLoop.position)
} else {
fun checkLoopRangeValues() {
}
when (loopvar.datatype) {
DataType.UBYTE -> {
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
@ -142,6 +149,22 @@ internal class AstChecker(private val program: Program,
}
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)
}
}
}
}
@ -311,8 +334,8 @@ internal class AstChecker(private val program: Program,
}
override fun visit(untilLoop: UntilLoop) {
if(untilLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
errors.err("condition value should be an integer type", untilLoop.untilCondition.position)
if(untilLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
errors.err("condition value should be an integer type", untilLoop.condition.position)
super.visit(untilLoop)
}
@ -353,6 +376,9 @@ internal class AstChecker(private val program: Program,
if (sourceVar?.struct != null) {
if (sourceVar.struct !== targetVar.struct)
errors.err("assignment of different struct types", assignment.position)
} else if(sourceVar?.isArray==true) {
if((sourceVar.value as ArrayLiteralValue).value.size != targetVar.struct?.numberOfElements)
errors.err("number of elements doesn't match struct definition", sourceVar.position)
}
}
}
@ -450,14 +476,11 @@ internal class AstChecker(private val program: Program,
}
override fun visit(decl: VarDecl) {
fun err(msg: String, position: Position?=null) {
errors.err(msg, position ?: decl.position)
}
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true)
err("recursive var declaration")
}
// CONST can only occur on simple types (byte, word, float)
if(decl.type== VarDeclType.CONST) {
@ -465,10 +488,12 @@ internal class AstChecker(private val program: Program,
err("const modifier can only be used on numeric types (byte, word, float)")
}
// FLOATS
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY) {
// FLOATS enabled?
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
err("floating point used, but that is not enabled via options")
}
if(decl.datatype == DataType.FLOAT && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE))
errors.warn("floating point values won't be placed in Zeropage due to size constraints", decl.position)
// ARRAY without size specifier MUST have an iterable initializer value
if(decl.isArray && decl.arraysize==null) {
@ -537,9 +562,11 @@ internal class AstChecker(private val program: Program,
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)
return
if(decl.type==VarDeclType.CONST) {
err("const declaration needs a compile-time constant initializer value, or range")
super.visit(decl)
return
}
}
}
}
@ -560,13 +587,13 @@ internal class AstChecker(private val program: Program,
}
}
if(decl.value !is NumericLiteralValue) {
err("value of memory var decl is not a numeric literal (it is a ${decl.value!!.javaClass.simpleName}).", decl.value?.position)
} else {
if(decl.value is NumericLiteralValue) {
val value = decl.value as NumericLiteralValue
if (value.type !in IntegerDatatypes || value.number.toInt() < 0 || value.number.toInt() > 65535) {
err("memory address must be valid integer 0..\$ffff", decl.value?.position)
}
} else {
err("value of memory mapped variable can only be a number, perhaps you meant to use an address pointer type instead?", decl.value?.position)
}
}
}
@ -685,9 +712,17 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty())
err("missing option directive argument(s)")
else if(directive.args.map{it.name in setOf("enable_floats", "force_output")}.any { !it })
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit")}.any { !it })
err("invalid option directive argument(s)")
}
"%target" -> {
if(directive.parent !is Block && directive.parent !is Module)
err("this directive may only occur in a block or at module level")
if(directive.args.size != 1)
err("directive requires one argument")
if(directive.args.single().name !in setOf(C64Target.name, Cx16Target.name))
err("invalid compilation target")
}
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
}
super.visit(directive)
@ -710,6 +745,17 @@ internal class AstChecker(private val program: Program,
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array)
}
fun isPassByReferenceElement(e: Expression): Boolean {
if(e is IdentifierReference) {
val decl = e.targetVarDecl(program.namespace)!!
return decl.datatype in PassByReferenceDatatypes
}
return e is StringLiteralValue
}
if(!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
errors.err("array literal contains invalid types", array.position)
super.visit(array)
}
@ -915,9 +961,6 @@ internal class AstChecker(private val program: Program,
val argIDt = arg.first.value.inferType(program)
if (!argIDt.isKnown)
return
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)
&& arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
errors.warn("calling a subroutine that expects X as a parameter is problematic. If you see a compiler error/crash about this later, try to change this call", position)
}
}
}
@ -1069,7 +1112,7 @@ internal class AstChecker(private val program: Program,
}
if(value.type.isUnknown)
return err("attempt to check values of array with as yet unknown datatype")
return false
when (targetDt) {
DataType.STR -> return err("string value expected")
@ -1138,7 +1181,7 @@ internal class AstChecker(private val program: Program,
// check if the floating point values are all within range
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
if(doubles.any { it < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || it > CompilationTarget.machine.FLOAT_MAX_POSITIVE })
if(doubles.any { it < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || it > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE })
return err("floating point value overflow")
return true
}

View File

@ -22,7 +22,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
}
override fun visit(block: Block) {
if(block.name in CompilationTarget.machine.opcodeNames)
if(block.name in CompilationTarget.instance.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
val existing = blocks[block.name]
@ -34,13 +34,23 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
super.visit(block)
}
override fun visit(directive: Directive) {
if(directive.directive=="%target") {
val compatibleTarget = directive.args.single().name
if (compatibleTarget != CompilationTarget.instance.name)
errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position)
}
super.visit(directive)
}
override fun visit(decl: VarDecl) {
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
if(decl.name in BuiltinFunctions)
errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in CompilationTarget.machine.opcodeNames)
if(decl.name in CompilationTarget.instance.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
if(decl.datatype==DataType.STRUCT) {
@ -74,7 +84,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
}
override fun visit(subroutine: Subroutine) {
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
if(subroutine.name in CompilationTarget.instance.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
@ -88,14 +98,6 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing)
// does the parameter redefine a variable declared elsewhere?
for(param in subroutine.parameters) {
val existingVar = subroutine.lookup(listOf(param.name), subroutine)
if (existingVar != null && existingVar.parent !== subroutine) {
nameError(param.name, param.position, existingVar)
}
}
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
@ -119,7 +121,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
}
override fun visit(label: Label) {
if(label.name in CompilationTarget.machine.opcodeNames)
if(label.name in CompilationTarget.instance.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) {

View File

@ -10,43 +10,6 @@ import prog8.ast.statements.*
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource == listOf("swap")) {
// 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.

View File

@ -82,6 +82,7 @@ interface IAstModification {
class SwapOperands(val expr: BinaryExpression): IAstModification {
override fun perform() {
require(expr.operator in associativeOperators)
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
@ -228,6 +229,7 @@ abstract class AstWalker {
track(before(decl, parent), decl, parent)
decl.value?.accept(this, decl)
decl.arraysize?.accept(this, decl)
decl.struct?.accept(this, decl)
track(after(decl, parent), decl, parent)
}
@ -348,7 +350,7 @@ abstract class AstWalker {
fun visit(untilLoop: UntilLoop, parent: Node) {
track(before(untilLoop, parent), untilLoop, parent)
untilLoop.untilCondition.accept(this, untilLoop)
untilLoop.condition.accept(this, untilLoop)
untilLoop.body.accept(this, untilLoop)
track(after(untilLoop, parent), untilLoop, parent)
}

View File

@ -33,6 +33,7 @@ interface IAstVisitor {
fun visit(decl: VarDecl) {
decl.value?.accept(this)
decl.arraysize?.accept(this)
decl.struct?.accept(this)
}
fun visit(subroutine: Subroutine) {
@ -115,7 +116,7 @@ interface IAstVisitor {
}
fun visit(untilLoop: UntilLoop) {
untilLoop.untilCondition.accept(this)
untilLoop.condition.accept(this)
untilLoop.body.accept(this)
}

View File

@ -71,23 +71,6 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val declConstValue = declValue.constValue(program)
if(declConstValue==null) {
// move the vardecl (without value) to the scope and replace this with a regular assignment
decl.value = null
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, declValue, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope() as Node)
)
}
}
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
@ -101,7 +84,7 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment)
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
val assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
} else {
@ -196,26 +179,47 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
when (structAssignment.value) {
is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null)
throw FatalAstException("can only assign arrays or structs to structs")
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
when {
sourceVar.struct!=null -> {
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
if(struct.statements.size!=sourceStruct.statements.size)
return listOf() // error will be printed elsewhere
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
sourceVar.isArray -> {
val array = (sourceVar.value as ArrayLiteralValue).value
if(struct.statements.size!=array.size)
return listOf() // error will be printed elsewhere
return struct.statements.zip(array).map {
val decl = it.first as VarDecl
val mangled = mangledStructMemberName(identifierName, decl.name)
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
val target = AssignTarget(targetName, null, null, structAssignment.position)
val assign = Assignment(target, it.second, structAssignment.position)
assign.linkParents(structAssignment)
assign
}
}
else -> {
throw FatalAstException("can only assign arrays or structs to structs")
}
}
}
is ArrayLiteralValue -> {

View File

@ -18,6 +18,21 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
val valueDt = declValue.inferType(program)
if(!valueDt.istype(decl.datatype)) {
return listOf(IAstModification.ReplaceNode(
declValue,
TypecastExpression(declValue, decl.datatype, true, declValue.position),
decl
))
}
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
@ -139,6 +154,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
call.args[arg.second.index],
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
call as Node)
break
}
}
}

View File

@ -26,6 +26,19 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
}
companion object {
private fun argTypeCompatible(argDt: DataType, paramDt: DataType): Boolean {
if(argDt==paramDt)
return true
// there are some exceptions that are considered compatible, such as STR <> UWORD
if(argDt==DataType.STR && paramDt==DataType.UWORD ||
argDt==DataType.UWORD && paramDt==DataType.STR)
return true
return false
}
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope)
@ -34,7 +47,7 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
if(call.args.size != target.parameters.size)
return "invalid number of arguments"
val paramtypes = target.parameters.map { it.type }
val mismatch = argtypes.zip(paramtypes).indexOfFirst { it.first != it.second}
val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
if(mismatch>=0) {
val actual = argtypes[mismatch].toString()
val expected = paramtypes[mismatch].toString()
@ -47,7 +60,8 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
return "invalid number of arguments"
val paramtypes = func.parameters.map { it.possibleDatatypes }
for (x in argtypes.zip(paramtypes).withIndex()) {
if (x.value.first !in x.value.second) {
val anyCompatible = x.value.second.any { argTypeCompatible(x.value.first, it) }
if (!anyCompatible) {
val actual = x.value.first.toString()
val expected = x.value.second.toString()
return "argument ${x.index + 1} type mismatch, was: $actual expected: $expected"

View File

@ -5,6 +5,7 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.compiler.target.CompilationTarget
sealed class Statement : Node {
@ -214,8 +215,9 @@ open class VarDecl(val type: VarDeclType,
DataType.UWORD -> DataType.ARRAY_UW
DataType.WORD -> DataType.ARRAY_W
DataType.FLOAT -> DataType.ARRAY_F
DataType.STR -> DataType.ARRAY_UW // use memory address of the string instead
else -> {
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats/strings(ptrs)", position))
DataType.ARRAY_UB
}
}
@ -301,6 +303,8 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
}
fun constIndex() = (index as? NumericLiteralValue)?.number?.toInt()
infix fun isSameAs(other: ArrayIndex) = index.isSameAs(other.index)
}
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
@ -391,8 +395,8 @@ data class AssignTarget(var identifier: IdentifierReference?,
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===identifier -> identifier = replacement as IdentifierReference
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
node === identifier -> identifier = replacement as IdentifierReference
node === arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
@ -413,16 +417,16 @@ data class AssignTarget(var identifier: IdentifierReference?,
}
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
if(identifier!=null) {
if (identifier != null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
}
if(arrayindexed!=null) {
if (arrayindexed != null) {
return arrayindexed!!.inferType(program)
}
if(memoryAddress!=null)
if (memoryAddress != null)
return InferredTypes.knownFor(DataType.UBYTE)
return InferredTypes.unknown()
@ -430,69 +434,93 @@ data class AssignTarget(var identifier: IdentifierReference?,
fun toExpression(): Expression {
return when {
identifier!=null -> identifier!!
arrayindexed!=null -> arrayindexed!!
memoryAddress!=null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
identifier != null -> identifier!!
arrayindexed != null -> arrayindexed!!
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
else -> throw FatalAstException("invalid assignmenttarget $this")
}
}
infix fun isSameAs(value: Expression): Boolean {
return when {
this.memoryAddress!=null -> {
memoryAddress != null -> {
// if the target is a memory write, and the value is a memory read, they're the same if the address matches
if(value is DirectMemoryRead)
if (value is DirectMemoryRead)
this.memoryAddress.addressExpression isSameAs value.addressExpression
else
false
}
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource &&
value.arrayspec.constIndex()!=null &&
arrayindexed!!.arrayspec.constIndex()!=null &&
value.arrayspec.constIndex()==arrayindexed!!.arrayspec.constIndex()
identifier != null -> value is IdentifierReference && value.nameInSource == identifier!!.nameInSource
arrayindexed != null -> {
if(value is ArrayIndexedExpression && value.identifier.nameInSource == arrayindexed!!.identifier.nameInSource)
arrayindexed!!.arrayspec isSameAs value.arrayspec
else
false
}
else -> false
}
}
fun isSameAs(other: AssignTarget, program: Program): Boolean {
if(this===other)
if (this === other)
return true
if(this.identifier!=null && other.identifier!=null)
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
if(this.memoryAddress!=null && other.memoryAddress!=null) {
if (this.identifier != null && other.identifier != null)
return this.identifier!!.nameInSource == other.identifier!!.nameInSource
if (this.memoryAddress != null && other.memoryAddress != null) {
val addr1 = this.memoryAddress.addressExpression.constValue(program)
val addr2 = other.memoryAddress.addressExpression.constValue(program)
return addr1!=null && addr2!=null && addr1==addr2
return addr1 != null && addr2 != null && addr1 == addr2
}
if(this.arrayindexed!=null && other.arrayindexed!=null) {
if(this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
if (this.arrayindexed != null && other.arrayindexed != null) {
if (this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
return x1!=null && x2!=null && x1==x2
return x1 != null && x2 != null && x1 == x2
}
}
return false
}
fun isNotMemory(namespace: INameScope): Boolean {
if(this.memoryAddress!=null)
return false
if(this.arrayindexed!=null) {
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!= VarDeclType.MEMORY
fun isInRegularRAM(namespace: INameScope): Boolean {
when {
this.memoryAddress != null -> {
return when (this.memoryAddress.addressExpression) {
is NumericLiteralValue -> {
CompilationTarget.instance.machine.isRegularRAMaddress((this.memoryAddress.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val decl = (this.memoryAddress.addressExpression as IdentifierReference).targetVarDecl(namespace)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
CompilationTarget.instance.machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
this.arrayindexed != null -> {
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
CompilationTarget.instance.machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
this.identifier != null -> {
val decl = this.identifier!!.targetVarDecl(namespace)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
CompilationTarget.instance.machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
}
if(this.identifier!=null) {
val targetStmt = this.identifier!!.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!= VarDeclType.MEMORY
}
return false
}
}
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
override lateinit var parent: Node
@ -657,6 +685,7 @@ class Subroutine(override val name: String,
}
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()
@ -814,19 +843,19 @@ class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override
}
class UntilLoop(var body: AnonymousScope,
var untilCondition: Expression,
var condition: Expression,
override val position: Position) : Statement() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
untilCondition.linkParents(this)
condition.linkParents(this)
body.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===untilCondition -> untilCondition = replacement as Expression
node===condition -> condition = replacement as Expression
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}

View File

@ -1,5 +1,6 @@
package prog8.compiler
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
@ -14,9 +15,15 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
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()
subroutineVariables.add(Pair(decl.name, decl))
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
if(nextAssign!=null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
decl.value = null
else
decl.value = decl.zeroElementValue()
}
return noModifications
}
@ -24,52 +31,78 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// Try to replace A = B <operator> Something by A= B, A = A <operator> Something
// this triggers the more efficent augmented assignment code generation more often.
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& assignment.target.isNotMemory(program.namespace)) {
&& assignment.target.isInRegularRAM(program.namespace)) {
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.operator !in comparisonOperators) {
if(binExpr.left !is BinaryExpression) {
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, parent),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) {
if (binExpr.right.referencesIdentifier(*assignment.target.identifier!!.nameInSource.toTypedArray())) {
// the right part of the expression contains the target variable itself.
// we can't 'split' it trivially because the variable will be changed halfway through.
if(binExpr.operator in associativeOperators) {
// A = <something-without-A> <associativeoperator> <otherthing-with-A>
// use the other part of the expression to split.
val assignRight = Assignment(assignment.target, binExpr.right, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignRight, parent),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
} else {
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, parent),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
}
}
}
return noModifications
}
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear()
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
subroutineVariables.addAll(decls.map { Pair(it.name, it) })
val sub = scope.definingSubroutine()
if (sub != null) {
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing != null) {
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
conflicts = true
}
}
if (!conflicts) {
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
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
val replaceVardecls =numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, initValue, it.position)
initValue.parent = assign
IAstModification.ReplaceNode(it, assign, scope)
}
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
return replaceVardecls + moveVardeclsUp
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val firstDeclarations = mutableMapOf<String, VarDecl>()
for(decl in subroutineVariables) {
val existing = firstDeclarations[decl.first]
if(existing!=null && existing !== decl.second) {
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
} else {
firstDeclarations[decl.first] = decl.second
}
}
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
@ -93,7 +126,6 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
}
return mods
}
@ -114,6 +146,18 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// 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.
// The only place for now where we can do this is for:
// asmsub register pair parameter.
if(typecast.type in WordDatatypes) {
val fcall = typecast.parent as? IFunctionCall
if (fcall != null) {
val sub = fcall.target.targetStatement(program.namespace) as? Subroutine
if (sub != null && sub.isAsmSubroutine) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
}
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
@ -129,4 +173,34 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
return noModifications
}
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
val binExpr = ifStatement.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// if x -> if x!=0, if x+5 -> if x+5 != 0
val booleanExpr = BinaryExpression(ifStatement.condition, "!=", NumericLiteralValue.optimalInteger(0, ifStatement.condition.position), ifStatement.condition.position)
return listOf(IAstModification.ReplaceNode(ifStatement.condition, booleanExpr, ifStatement))
}
return noModifications
}
override fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val binExpr = untilLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// until x -> until x!=0, until x+5 -> until x+5 != 0
val booleanExpr = BinaryExpression(untilLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, untilLoop.condition.position), untilLoop.condition.position)
return listOf(IAstModification.ReplaceNode(untilLoop.condition, booleanExpr, untilLoop))
}
return noModifications
}
override fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val binExpr = whileLoop.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in comparisonOperators) {
// while x -> while x!=0, while x+5 -> while x+5 != 0
val booleanExpr = BinaryExpression(whileLoop.condition, "!=", NumericLiteralValue.optimalInteger(0, whileLoop.condition.position), whileLoop.condition.position)
return listOf(IAstModification.ReplaceNode(whileLoop.condition, booleanExpr, whileLoop))
}
return noModifications
}
}

View File

@ -27,7 +27,8 @@ data class CompilationOptions(val output: OutputType,
val launcher: LauncherType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean)
val floats: Boolean,
val noSysInit: Boolean)
class CompilerException(message: String?) : Exception(message)

View File

@ -4,7 +4,10 @@ import prog8.ast.AstToSourceCode
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.statements.Directive
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.optimizer.*
import prog8.optimizer.UnusedCodeRemover
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
@ -13,6 +16,7 @@ import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.moduleName
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
@ -25,12 +29,22 @@ class CompilationResult(val success: Boolean,
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
compilationTarget: String,
outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
when(compilationTarget) {
C64Target.name -> CompilationTarget.instance = C64Target
Cx16Target.name -> CompilationTarget.instance = Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
@ -78,7 +92,7 @@ fun compileProgram(filepath: Path,
}
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
println("Parsing...")
println("Compiler target: ${CompilationTarget.instance.name}. Parsing...")
val importer = ModuleImporter()
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
importer.importModule(programAst, filepath)
@ -90,8 +104,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.")
// depending on the mach9ine and compiler options we may have to include some libraries
CompilationTarget.machine.importLibs(compilerOptions, importer, programAst)
// depending on the machine and compiler options we may have to include some libraries
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
// always import prog8lib and math
importer.importLibraryModule(programAst, "math")
@ -112,7 +126,8 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val zpType: ZeropageType =
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
@ -122,6 +137,12 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && CompilationTarget.instance.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
@ -129,16 +150,25 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
.map { it[0].int!!..it[1].int!! }
.toList()
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
System.err.println("invalid output type $outputType")
exitProcess(1)
}
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled
zpType, zpReserved, floatsEnabled, noSysInit
)
}
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing...")
println("Processing for target ${CompilationTarget.instance.name}...")
programAst.checkIdentifiers(errors)
errors.handle()
programAst.constantFold(errors)
@ -160,13 +190,13 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements(errors)
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away:
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
errors.handle()
if (optsDone1 + optsDone2 == 0)
break
}
val remover = UnusedCodeRemover(errors)
val remover = UnusedCodeRemover(programAst, errors)
remover.visit(programAst)
remover.applyModifications()
errors.handle()
@ -191,11 +221,11 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
// printAst(programAst)
CompilationTarget.machine.initializeZeropage(compilerOptions)
val assembly = CompilationTarget.asmGenerator(
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
val assembly = CompilationTarget.instance.asmGenerator(
programAst,
errors,
CompilationTarget.machine.zeropage,
CompilationTarget.instance.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)

View File

@ -10,7 +10,6 @@ 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
@ -71,12 +70,4 @@ abstract class Zeropage(protected val options: CompilationOptions) {
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
enum class ExitProgramStrategy {
CLEAN_EXIT,
SYSTEM_RESET
}
abstract val exitProgramStrategy: ExitProgramStrategy
}

View File

@ -4,15 +4,44 @@ import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
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 java.nio.file.Path
internal interface CompilationTarget {
val name: String
val machine: IMachineDefinition
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator
companion object {
lateinit var name: String
lateinit var machine: IMachineDefinition
lateinit var encodeString: (str: String, altEncoding: Boolean) -> List<Short>
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
lateinit var asmGenerator: (Program, ErrorReporter, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
lateinit var instance: CompilationTarget
}
}
internal object C64Target: CompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
AsmGen(program, errors, zp, options, path)
}
internal object Cx16Target: CompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
AsmGen(program, errors, zp, options, path)
}

View File

@ -11,6 +11,11 @@ internal interface IMachineFloat {
fun makeFloatFillAsm(): String
}
internal enum class CpuType {
CPU6502,
CPU65c02
}
internal interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
@ -23,12 +28,12 @@ internal interface IMachineDefinition {
val opcodeNames: Set<String>
var zeropage: Zeropage
val initSystemProcname: String
val cpu: String
val cpu: CpuType
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)
fun isRegularRAMaddress(address: Int): Boolean
}

View File

@ -2,6 +2,7 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path
@ -22,12 +23,12 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating prg.")
println("\nCreating prg for target ${CompilationTarget.instance.name}.")
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary.")
println("\nCreating raw binary for target ${CompilationTarget.instance.name}.")
binFile
}
}

View File

@ -2,6 +2,7 @@ package prog8.compiler.target.c64
import prog8.ast.Program
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat
import prog8.parser.ModuleImporter
@ -12,7 +13,7 @@ import kotlin.math.pow
internal object C64MachineDefinition: IMachineDefinition {
override val cpu = "6502"
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
@ -28,7 +29,6 @@ internal object C64MachineDefinition: IMachineDefinition {
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
override lateinit var zeropage: Zeropage
override val initSystemProcname = "c64.init_system"
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
@ -37,28 +37,29 @@ internal object C64MachineDefinition: IMachineDefinition {
val mflpt5 = Mflpt5.fromNumber(number)
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
when {
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.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"
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ZERO_const" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ONE_const" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "floats.FL_PIVAL"
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_N32768"
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FONE"
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRHLF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRTWO"
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_NEGHLF"
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "floats.FL_LOG2"
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "floats.FL_TENC"
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "floats.FL_NZMIL"
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FHALF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "floats.FL_LOGEB2"
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_PIHALF"
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_TWOPI"
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FR4"
else -> {
// attempt to correct for a few rounding issues
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
3.1415926536 -> return "c64flt.FL_PIVAL"
1.4142135624 -> return "c64flt.FL_SQRTWO"
0.7071067812 -> return "c64flt.FL_SQRHLF"
0.6931471806 -> return "c64flt.FL_LOG2"
3.1415926536 -> return "floats.FL_PIVAL"
1.4142135624 -> return "floats.FL_SQRTWO"
0.7071067812 -> return "floats.FL_SQRHLF"
0.6931471806 -> return "floats.FL_LOG2"
else -> {}
}
}
@ -68,7 +69,7 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "c64lib")
importer.importLibraryModule(program, "syslib")
}
override fun launchEmulator(programName: String) {
@ -88,6 +89,8 @@ internal object C64MachineDefinition: IMachineDefinition {
}
}
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
}
@ -108,17 +111,10 @@ internal object C64MachineDefinition: IMachineDefinition {
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
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
@ -126,7 +122,7 @@ internal object C64MachineDefinition: IMachineDefinition {
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9)
free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
@ -148,7 +144,7 @@ internal object C64MachineDefinition: IMachineDefinition {
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list
free.removeAll(listOf(
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
@ -169,7 +165,6 @@ internal object C64MachineDefinition: IMachineDefinition {
}
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)

View File

@ -9,6 +9,7 @@ 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
@ -34,6 +35,10 @@ internal class AsmGen(private val program: Program,
val options: CompilationOptions,
private val outputDir: Path): IAssemblyGenerator {
// for expressions and augmented assignments:
val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40,50,80,100)
val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40,50,80,100)
private val assemblyLines = mutableListOf<String>()
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
private val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
@ -42,10 +47,10 @@ internal class AsmGen(private val program: Program,
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
internal val loopEndLabels = ArrayDeque<String>()
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
assemblyLines.clear()
@ -76,9 +81,14 @@ internal class AsmGen(private val program: Program,
return AssemblyProgram(program.name, outputDir)
}
private fun header() {
val ourName = this.javaClass.name
val cpu = CompilationTarget.machine.cpu
val cpu = when(CompilationTarget.instance.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)}")
@ -89,18 +99,16 @@ internal class AsmGen(private val program: Program,
program.actualLoadAddress = program.definedLoadAddress
if (program.actualLoadAddress == 0) // fix load address
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
CompilationTarget.machine.BASIC_LOAD_ADDRESS else CompilationTarget.machine.RAW_LOAD_ADDRESS
CompilationTarget.instance.machine.BASIC_LOAD_ADDRESS else CompilationTarget.instance.machine.RAW_LOAD_ADDRESS
// the global prog8 variables needed
val zp = CompilationTarget.machine.zeropage
val initproc = CompilationTarget.machine.initSystemProcname
val zp = CompilationTarget.instance.machine.zeropage
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()}")
out("P8ESTACK_LO = ${CompilationTarget.instance.machine.ESTACK_LO.toHex()}")
out("P8ESTACK_HI = ${CompilationTarget.instance.machine.ESTACK_HI.toHex()}")
when {
options.launcher == LauncherType.BASIC -> {
@ -113,18 +121,14 @@ internal class AsmGen(private val program: Program,
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
out(" tsx")
out(" stx prog8_lib.orig_stackpointer")
if(!initproc.isNullOrEmpty())
out(" jsr $initproc")
if(!options.noSysInit)
out(" jsr ${CompilationTarget.instance.name}.init_system")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
out(" tsx")
out(" stx prog8_lib.orig_stackpointer")
if(!initproc.isNullOrEmpty())
out(" jsr $initproc")
if(!options.noSysInit)
out(" jsr ${CompilationTarget.instance.name}.init_system")
}
options.output == OutputType.RAW -> {
out("; ---- raw assembler program ----")
@ -132,40 +136,23 @@ internal class AsmGen(private val program: Program,
}
}
if (zeropage.exitProgramStrategy != Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
// disable shift-commodore charset switching and run/stop key
out(" lda #$80")
out(" lda #$80")
out(" sta 657\t; disable charset switching")
out(" lda #239")
out(" sta 808\t; disable run/stop key")
if(options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
out("""
; zeropage is clobbered so we need to reset the machine at exit
lda #>${CompilationTarget.instance.name}.reset_system
pha
lda #<${CompilationTarget.instance.name}.reset_system
pha""")
}
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block that has globals")
program.allBlocks().forEach {
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
out(" jsr ${it.name}.prog8_init_vars")
}
out(" clc")
when (zeropage.exitProgramStrategy) {
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
out(" jmp main.start\t; jump to program entrypoint")
}
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
out(" jsr main.start\t; call program entrypoint")
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out(" jmp main.start ; start program / force start proc to be included")
}
private fun footer() {
// the global list of all floating point constants for the whole program
out("; global float constants")
for (flt in globalFloatConsts) {
val floatFill = CompilationTarget.machine.getFloat(flt.key).makeFloatFillAsm()
val floatFill = CompilationTarget.instance.machine.getFloat(flt.key).makeFloatFillAsm()
val floatvalue = flt.key
out("${flt.value}\t.byte $floatFill ; float $floatvalue")
}
@ -335,7 +322,7 @@ internal class AsmGen(private val program: Program,
}
val floatFills = array.map {
val number = (it as NumericLiteralValue).number
CompilationTarget.machine.getFloat(number).makeFloatFillAsm()
CompilationTarget.instance.machine.getFloat(number).makeFloatFillAsm()
}
out(name)
for (f in array.zip(floatFills))
@ -348,7 +335,10 @@ internal class AsmGen(private val program: Program,
out("\n; memdefs and kernel subroutines")
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
for(m in memvars) {
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
if(m.value is NumericLiteralValue)
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
else
out(" ${m.name} = ${asmVariableName((m.value as AddressOf).identifier)}")
}
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
for(sub in asmSubs) {
@ -416,10 +406,17 @@ internal class AsmGen(private val program: Program,
"$"+number.toString(16).padStart(2, '0')
}
DataType.ARRAY_UW -> array.map {
if(it is NumericLiteralValue) {
"$" + it.number.toInt().toString(16).padStart(4, '0')
} else {
(it as AddressOf).identifier.nameInSource.joinToString(".")
when (it) {
is NumericLiteralValue -> {
"$" + it.number.toInt().toString(16).padStart(4, '0')
}
is AddressOf -> {
it.identifier.firstStructVarName(program.namespace) ?: asmSymbolName(it.identifier)
}
is IdentifierReference -> {
it.firstStructVarName(program.namespace) ?: asmSymbolName(it)
}
else -> throw AssemblyError("weird array elt dt")
}
}
else -> throw AssemblyError("invalid arraysize type")
@ -469,7 +466,7 @@ internal class AsmGen(private val program: Program,
}
internal fun getFloatAsmConst(number: Double): String {
var asmName = CompilationTarget.machine.getFloatRomConst(number)
var asmName = CompilationTarget.instance.machine.getFloatRomConst(number)
if(asmName.isNullOrEmpty()) {
// no ROM float const for this value, create our own
asmName = globalFloatConsts[number]
@ -543,22 +540,38 @@ internal class AsmGen(private val program: Program,
internal fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
private val saveRegisterLabels = Stack<String>();
internal fun saveRegister(register: CpuRegister) {
when(register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> out(" txa | pha")
CpuRegister.Y -> out(" tya | pha")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
else out(" stx _prog8_regsave${register.name}")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
else out(" sty _prog8_regsave${register.name}")
}
}
}
internal fun restoreRegister(register: CpuRegister) {
when(register) {
CpuRegister.A -> out(" pla")
CpuRegister.X -> out(" pla | tax")
CpuRegister.Y -> out(" pla | tay")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
else out(" ldx _prog8_regsave${register.name}")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
else out(" ldy _prog8_regsave${register.name}")
}
}
}
internal fun translate(stmt: Statement) {
outputSourceLine(stmt)
when(stmt) {
@ -572,15 +585,15 @@ internal class AsmGen(private val program: Program,
is FunctionCallStatement -> {
val functionName = stmt.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
if(builtinFunc!=null) {
if (builtinFunc != null) {
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
} else {
functioncallAsmGen.translateFunctionCall(stmt)
// discard any results from the stack:
val sub = stmt.target.targetSubroutine(program.namespace)!!
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for((t, reg) in returns) {
if(reg.stack) {
for ((t, reg) in returns) {
if (reg.stack) {
if (t in IntegerDatatypes || t in PassByReferenceDatatypes) out(" inx")
else if (t == DataType.FLOAT) out(" inx | inx | inx")
}
@ -669,7 +682,7 @@ internal class AsmGen(private val program: Program,
+""")
when(register) {
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
CpuRegister.X -> throw AssemblyError("can't use X here")
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
}
}
@ -705,10 +718,40 @@ internal class AsmGen(private val program: Program,
}
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")
when(elementDt) {
in ByteDatatypes -> {
when (register) {
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
}
}
in WordDatatypes -> {
out("""
inx
lda P8ESTACK_LO,x
asl a""")
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
inx
lda P8ESTACK_LO,x
asl a
asl a
clc
adc P8ESTACK_LO,x""")
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
}
}
}
@ -743,9 +786,30 @@ internal class AsmGen(private val program: Program,
out("${sub.name}\t.proc")
zeropagevars2asm(sub.statements)
memdefs2asm(sub.statements)
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
if(sub.name=="start" && sub.definingBlock().name=="main") {
out("; program startup initialization")
out(" cld")
program.allBlocks().forEach {
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
out(" jsr ${it.name}.prog8_init_vars")
}
out("""
tsx
stx prog8_lib.orig_stackpointer ; required for func_exit
ldx #255 ; init estack ptr
clv
clc""")
}
out("; statements")
sub.statements.forEach{ translate(it) }
out("; variables")
out("""
; register saves
_prog8_regsaveX .byte 0
_prog8_regsaveY .byte 0""") // TODO only generate these bytes if they're actually used by saveRegister()
vardecls2asm(sub.statements)
out(" .pend\n")
}
@ -777,25 +841,32 @@ internal class AsmGen(private val program: Program,
}
private fun translate(stmt: IfStatement) {
expressionsAsmGen.translateExpression(stmt.condition)
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
val elseLabel = makeLabel("if_else")
val endLabel = makeLabel("if_end")
out(" beq $elseLabel")
translate(stmt.truepart)
out(" jmp $endLabel")
out(elseLabel)
translate(stmt.elsepart)
out(endLabel)
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
if (stmt.elsepart.containsNoCodeNorVars()) {
// empty else
val endLabel = makeLabel("if_end")
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.truepart)
out(endLabel)
}
else {
// both true and else parts
val elseLabel = makeLabel("if_else")
val endLabel = makeLabel("if_end")
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, elseLabel)
translate(stmt.truepart)
out(" jmp $endLabel")
out(elseLabel)
translate(stmt.elsepart)
out(endLabel)
}
}
private fun translateTestStack(dataType: DataType) {
when(dataType) {
in ByteDatatypes -> out(" inx | lda P8ESTACK_LO,x")
in WordDatatypes -> out(" inx | lda P8ESTACK_LO,x | ora P8ESTACK_HI,x")
DataType.FLOAT -> throw AssemblyError("conditional value should be an integer (boolean)")
else -> throw AssemblyError("non-numerical dt")
}
private fun checkBooleanExpression(condition: Expression) {
if(condition !is BinaryExpression || condition.operator !in comparisonOperators)
throw AssemblyError("expected boolean expression $condition")
}
private fun translate(stmt: RepeatLoop) {
@ -914,25 +985,13 @@ $counterVar .byte 0""")
}
private fun translate(stmt: WhileLoop) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend")
loopEndLabels.push(endLabel)
out(whileLabel)
expressionsAsmGen.translateExpression(stmt.condition)
val conditionDt = stmt.condition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" inx | lda P8ESTACK_LO,x | beq $endLabel")
} else {
out("""
inx
lda P8ESTACK_LO,x
bne +
lda P8ESTACK_HI,x
beq $endLabel
+ """)
}
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.body)
out(" jmp $whileLabel")
out(endLabel)
@ -940,26 +999,14 @@ $counterVar .byte 0""")
}
private fun translate(stmt: UntilLoop) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
out(repeatLabel)
translate(stmt.body)
expressionsAsmGen.translateExpression(stmt.untilCondition)
val conditionDt = stmt.untilCondition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" inx | lda P8ESTACK_LO,x | beq $repeatLabel")
} else {
out("""
inx
lda P8ESTACK_LO,x
bne +
lda P8ESTACK_HI,x
beq $repeatLabel
+ """)
}
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, repeatLabel)
out(endLabel)
loopEndLabels.pop()
}
@ -999,6 +1046,7 @@ $counterVar .byte 0""")
}
}
}
out(" jmp $endLabel")
for(choiceBlock in choiceBlocks) {
out(choiceBlock.first)
translate(choiceBlock.second)

View File

@ -151,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 floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
val nineth = pair[8].value.trimStart()
val tenth = pair[9].value.trimStart()
@ -161,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 floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
// identical float init
@ -177,6 +179,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
}
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// TODO not sure if this is correct in all situations....:
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {

View File

@ -4,8 +4,14 @@ import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.codegen.assignment.SourceStorageKind
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
import prog8.compiler.toHex
import prog8.functions.FSignature
@ -342,7 +348,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) {
translateFunctionArguments(fcall.args, func)
asmgen.out(" jsr c64flt.func_$functionName")
asmgen.out(" jsr floats.func_$functionName")
}
private fun funcSgn(fcall: IFunctionCall, func: FSignature) {
@ -353,7 +359,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
DataType.WORD -> asmgen.out(" jsr math.sign_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.sign_f")
DataType.FLOAT -> asmgen.out(" jsr floats.sign_f")
else -> throw AssemblyError("weird type $dt")
}
}
@ -364,7 +370,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
@ -377,7 +383,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
@ -395,12 +401,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcSwap(fcall: IFunctionCall) {
val first = fcall.args[0]
val second = fcall.args[1]
// optimized simple case: swap two variables
if(first is IdentifierReference && second is IdentifierReference) {
val firstName = asmgen.asmVariableName(first)
val secondName = asmgen.asmVariableName(second)
val dt = first.inferType(program)
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | tya | sta $secondName")
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName")
return
}
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
@ -426,14 +434,196 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
sta P8ZP_SCRATCH_W2
lda #>$secondName
sta P8ZP_SCRATCH_W2+1
jsr c64flt.swap_floats
jsr floats.swap_floats
""")
return
}
}
// other types of swap() calls should have been replaced by a different statement sequence involving a temp variable
throw AssemblyError("no asm generation for swap funccall $fcall")
// optimized simple case: swap two memory locations
if(first is DirectMemoryRead && second is DirectMemoryRead) {
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
val name2 = if(second.addressExpression is IdentifierReference) asmgen.asmVariableName(second.addressExpression as IdentifierReference) else null
when {
addr1!=null && addr2!=null -> {
asmgen.out(" ldy $addr1 | lda $addr2 | sta $addr1 | sty $addr2")
return
}
addr1!=null && name2!=null -> {
asmgen.out(" ldy $addr1 | lda $name2 | sta $addr1 | sty $name2")
return
}
name1!=null && addr2 != null -> {
asmgen.out(" ldy $name1 | lda $addr2 | sta $name1 | sty $addr2")
return
}
name1!=null && name2!=null -> {
asmgen.out(" ldy $name1 | lda $name2 | sta $name1 | sty $name2")
return
}
}
}
if(first is ArrayIndexedExpression && second is ArrayIndexedExpression) {
val indexValue1 = first.arrayspec.index as? NumericLiteralValue
val indexName1 = first.arrayspec.index as? IdentifierReference
val indexValue2 = second.arrayspec.index as? NumericLiteralValue
val indexName2 = second.arrayspec.index as? IdentifierReference
val arrayVarName1 = asmgen.asmVariableName(first.identifier)
val arrayVarName2 = asmgen.asmVariableName(second.identifier)
val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT)
if(indexValue1!=null && indexValue2!=null) {
swapArrayValues(elementDt, arrayVarName1, indexValue1, arrayVarName2, indexValue2)
return
} else if(indexName1!=null && indexName2!=null) {
swapArrayValues(elementDt, arrayVarName1, indexName1, arrayVarName2, indexName2)
return
}
}
// all other types of swap() calls are done via the evaluation stack
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
return when (expr) {
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, variable=expr)
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, array = expr)
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, memory = DirectMemoryWrite(expr.addressExpression, expr.position))
else -> throw AssemblyError("invalid expression object $expr")
}
}
asmgen.translateExpression(first)
asmgen.translateExpression(second)
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, datatype),
targetFromExpr(first, datatype),
false, first.position
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, datatype),
targetFromExpr(second, datatype),
false, second.position
)
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val index1 = indexValue1.number.toInt() * elementDt.memorySize()
val index2 = indexValue2.number.toInt() * elementDt.memorySize()
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
lda $arrayVarName1+$index1
ldy $arrayVarName2+$index2
sta $arrayVarName2+$index2
sty $arrayVarName1+$index1
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
lda $arrayVarName1+$index1
ldy $arrayVarName2+$index2
sta $arrayVarName2+$index2
sty $arrayVarName1+$index1
lda $arrayVarName1+$index1+1
ldy $arrayVarName2+$index2+1
sta $arrayVarName2+$index2+1
sty $arrayVarName1+$index1+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1
lda #>(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1+1
lda #<(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2
lda #>(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2+1
jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexName2: IdentifierReference) {
val idxAsmName1 = asmgen.asmVariableName(indexName1)
val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
ldx $idxAsmName1
ldy $idxAsmName2
lda $arrayVarName1,x
pha
lda $arrayVarName2,y
sta $arrayVarName1,x
pla
sta $arrayVarName2,y
ldx P8ZP_SCRATCH_REG
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
lda $idxAsmName1
asl a
tax
lda $idxAsmName2
asl a
tay
lda $arrayVarName1,x
pha
lda $arrayVarName2,y
sta $arrayVarName1,x
pla
sta $arrayVarName2,y
lda $arrayVarName1+1,x
pha
lda $arrayVarName2+1,y
sta $arrayVarName1+1,x
pla
sta $arrayVarName2+1,y
ldx P8ZP_SCRATCH_REG
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #>$arrayVarName1
sta P8ZP_SCRATCH_W1+1
lda $idxAsmName1
asl a
asl a
clc
adc $idxAsmName1
adc #<$arrayVarName1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ lda #>$arrayVarName2
sta P8ZP_SCRATCH_W2+1
lda $idxAsmName2
asl a
asl a
clc
adc $idxAsmName2
adc #<$arrayVarName2
sta P8ZP_SCRATCH_W2
bcc +
inc P8ZP_SCRATCH_W2+1
+ jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
}
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
@ -442,7 +632,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
DataType.FLOAT -> asmgen.out(" jsr floats.abs_f")
else -> throw AssemblyError("weird type")
}
}

View File

@ -46,7 +46,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stepsize==1 || stepsize==-1) {
// bytes, step 1 or -1
// bytes array, step 1 or -1
val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar
@ -258,13 +258,6 @@ $endLabel inx""")
asmgen.loopEndLabels.pop()
}
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) {
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), variable=stmt.loopVar)
val src = AsmAssignSource.fromAstSource(range.from, program).adjustDataTypeToTarget(target)
val assign = AsmAssignment(src, target, false, range.position)
asmgen.translateNormalAssignment(assign)
}
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
@ -615,4 +608,11 @@ $loopLabel""")
$endLabel""")
asmgen.loopEndLabels.pop()
}
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) {
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), variable=stmt.loopVar)
val src = AsmAssignSource.fromAstSource(range.from, program).adjustDataTypeToTarget(target)
val assign = AsmAssignment(src, target, false, range.position)
asmgen.translateNormalAssignment(assign)
}
}

View File

@ -4,6 +4,7 @@ import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError
@ -16,9 +17,9 @@ 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}")
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult()
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
if(saveX)
asmgen.out(" stx P8ZP_SCRATCH_REG_X") // we only save X for now (required! is the eval stack pointer), screw A and Y...
asmgen.saveRegister(CpuRegister.X)
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
@ -57,39 +58,85 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.out(" jsr $subName")
if(saveX)
asmgen.out(" ldx P8ZP_SCRATCH_REG_X") // restore X again
asmgen.restoreRegister(CpuRegister.X)
}
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.
if(sub.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
for (regparam in sub.asmParameterRegisters) {
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
asmgen.out(" inx") // align estack pointer
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
when {
regparam.statusflag==Statusflag.Pc -> {
asmgen.out("""
inx
pha
lda P8ESTACK_LO,x
beq +
sec
bcs ++
+ clc
+ pla""")
argi.value.second.stack -> TODO("asmsub @stack parameter")
argi.value.second.statusflag == Statusflag.Pc -> {
require(argForCarry == null)
argForCarry = argi
}
regparam.statusflag!=null -> {
throw AssemblyError("can only use Carry as status flag parameter")
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
require(argForXregister==null)
argForXregister = argi
}
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)
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
require(argForAregister == null)
argForAregister = argi
}
else -> {}
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
asmgen.out(" ldy P8ESTACK_LO+${argi.index},x")
}
else -> throw AssemblyError("weird argument")
}
}
if(argForCarry!=null) {
asmgen.out("""
lda P8ESTACK_LO+${argForCarry.index},x
beq +
sec
bcs ++
+ clc
+ php""") // push the status flags
}
if(argForAregister!=null) {
when(argForAregister.value.second.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x")
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x | ldy P8ESTACK_HI+${argForAregister.index},x")
else -> throw AssemblyError("weird arg")
}
}
if(argForXregister!=null) {
if(argForAregister!=null)
asmgen.out(" pha")
when(argForXregister.value.second.registerOrPair) {
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO+${argForXregister.index},x | tax")
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO+${argForXregister.index},x | lda P8ESTACK_HI+${argForXregister.index},x | tax | tya")
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI+${argForXregister.index},x | lda P8ESTACK_LO+${argForXregister.index},x | tax")
else -> throw AssemblyError("weird arg")
}
if(argForAregister!=null)
asmgen.out(" pla")
} else {
repeat(sub.parameters.size - 1) { asmgen.out(" inx") } // unwind stack
}
if(argForCarry!=null)
asmgen.out(" plp") // set the carry flag back to correct value
}
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {

View File

@ -33,7 +33,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
DataType.FLOAT -> {
asmgen.out(" lda #<$what | ldy #>$what")
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
@ -90,14 +90,15 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
DataType.FLOAT -> {
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
else -> {
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.out(" stx P8ZP_SCRATCH_REG_X | tax")
asmgen.saveRegister(CpuRegister.X)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
@ -120,11 +121,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
adc #<$asmArrayvarname
bcc +
iny
+ jsr c64flt.inc_var_f""")
+ jsr floats.inc_var_f""")
}
else -> throw AssemblyError("weird array elt dt")
}
asmgen.out(" ldx P8ZP_SCRATCH_REG_X")
asmgen.restoreRegister(CpuRegister.X)
}
}
}

View File

@ -41,7 +41,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! }
val vardecl by lazy { variable!!.targetVarDecl(program.namespace)!! }
val asmVarname by lazy {
if(variable!=null)
asmgen.asmVariableName(variable)
@ -159,6 +159,7 @@ internal class AsmAssignment(val source: AsmAssignSource,
val position: Position) {
init {
require(source.datatype==target.datatype) {"source and target datatype must be identical"}
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype.memorySize() == target.datatype.memorySize()) { "source and target datatype must be same storage class" }
}
}

View File

@ -5,13 +5,16 @@ 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.target.c64.codegen.ExpressionsAsmGen
import prog8.compiler.toHex
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen, private val exprAsmgen: ExpressionsAsmGen) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen)
fun translate(assignment: Assignment) {
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
@ -63,7 +66,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
in WordDatatypes ->
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
DataType.FLOAT ->
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float")
else ->
throw AssemblyError("weird array type")
}
@ -85,7 +88,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
adc #<$arrayVarName
bcc +
iny
+ jsr c64flt.push_float""")
+ jsr floats.push_float""")
}
else ->
throw AssemblyError("weird array elt type")
@ -118,17 +121,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
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 TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, assign)
// is FunctionCall -> {
// if (assign.target.kind == TargetStorageKind.STACK) {
// asmgen.translateExpression(value)
@ -159,6 +152,52 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: Expression, origAssign: AsmAssignment) {
val valueDt = value.inferType(program).typeOrElse(DataType.STRUCT)
when(value) {
is IdentifierReference -> {
if (valueDt == DataType.UBYTE || valueDt == DataType.BYTE) {
if(targetDt in WordDatatypes) {
assignVariableByteIntoWord(target, value, valueDt)
return
}
}
}
is DirectMemoryRead -> {
if(targetDt in WordDatatypes) {
if (value.addressExpression is NumericLiteralValue) {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
assignMemoryByteIntoWord(target, address, null)
return
}
else if (value.addressExpression is IdentifierReference) {
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
return
}
}
}
is NumericLiteralValue -> throw AssemblyError("a cast of a literal value should have been const-folded away")
else -> {}
}
when(value) {
is PrefixExpression -> {}
is BinaryExpression -> {}
is ArrayIndexedExpression -> {}
is TypecastExpression -> {}
is RangeExpr -> {}
is FunctionCall -> {}
else -> {
// TODO optimize the others further?
println("warning: slow stack evaluation used for typecast: into $targetDt at ${value.position}")
}
}
// give up, do it via eval stack
asmgen.translateExpression(origAssign.source.expression!!)
assignStackValue(target)
}
private fun assignStackValue(target: AsmAssignTarget) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -179,7 +218,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out("""
lda #<${target.asmVarname}
ldy #>${target.asmVarname}
jsr c64flt.pop_float
jsr floats.pop_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
@ -211,7 +250,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out("""
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
jsr c64flt.pop_float
jsr floats.pop_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
@ -241,7 +280,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
adc #<${target.asmVarname}
bcc +
iny
+ jsr c64flt.pop_float""")
+ jsr floats.pop_float""")
}
else -> throw AssemblyError("weird dt")
}
@ -258,16 +297,18 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
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.X -> throw AssemblyError("can't load X from stack here - use intermediary var? ${target.origAstTarget?.position}")
RegisterOrPair.Y -> asmgen.out(" inx | ldy P8ESTACK_LO,x")
else -> throw AssemblyError("can't assign byte to register pair word")
RegisterOrPair.AX -> asmgen.out(" inx | lda P8ESTACK_LO,x | ldx #0")
RegisterOrPair.AY -> asmgen.out(" inx | lda P8ESTACK_LO,x | ldy #0")
else -> throw AssemblyError("can't assign byte from stack to register pair XY")
}
}
DataType.UWORD, DataType.WORD, in PassByReferenceDatatypes -> {
when(target.register!!) {
RegisterOrPair.AX -> throw AssemblyError("can't use X here")
RegisterOrPair.AX -> throw AssemblyError("can't load X from stack here - use intermediary var? ${target.origAstTarget?.position}")
RegisterOrPair.AY-> asmgen.out(" inx | lda P8ESTACK_LO,x | ldy P8ESTACK_HI,x")
RegisterOrPair.XY-> throw AssemblyError("can't use X here")
RegisterOrPair.XY-> throw AssemblyError("can't load X from stack here - use intermediary var? ${target.origAstTarget?.position}")
else -> throw AssemblyError("can't assign word to single byte register")
}
}
@ -279,18 +320,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignAddressOf(target: AsmAssignTarget, name: IdentifierReference) {
val struct = name.memberOfStruct(program.namespace)
val sourceName = if (struct != null) {
// take the address of the first struct member instead
val decl = name.targetVarDecl(program.namespace)!!
val firstStructMember = struct.nameOfFirstMember()
// find the flattened var that belongs to this first struct member
val firstVarName = listOf(decl.name, firstStructMember)
val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl
firstVar.name
} else {
asmgen.fixNameSymbols(name.nameInSource.joinToString("."))
}
val sourceName = name.firstStructVarName(program.namespace) ?: asmgen.fixNameSymbols(name.nameInSource.joinToString("."))
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -366,7 +396,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
jsr c64flt.copy_float
jsr floats.copy_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
@ -399,7 +429,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
adc #<${target.asmVarname}
bcc +
iny
+ jsr c64flt.copy_float""")
+ jsr floats.copy_float""")
}
else -> throw AssemblyError("weird dt")
}
@ -456,13 +486,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// TODO("array[var] ${target.constArrayIndexValue}")
// }
val index = target.array!!.arrayspec.index
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr floats.push_float")
asmgen.translateExpression(index)
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname} | jsr c64flt.pop_float_to_indexed_var")
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname} | jsr floats.pop_float_to_indexed_var")
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
TargetStorageKind.STACK -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
TargetStorageKind.STACK -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr floats.push_float")
}
}
@ -516,6 +546,59 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignVariableByteIntoWord(wordtarget: AsmAssignTarget, bytevar: IdentifierReference, valueDt: DataType) {
if(valueDt == DataType.BYTE)
TODO("sign extend byte to word")
val sourceName = asmgen.asmVariableName(bytevar)
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda $sourceName
sta ${wordtarget.asmVarname}
lda #0
sta ${wordtarget.asmVarname}+1
""")
}
TargetStorageKind.ARRAY -> {
val index = wordtarget.array!!.arrayspec.index
when {
wordtarget.constArrayIndexValue!=null -> {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx | lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1")
}
index is IdentifierReference -> {
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array, wordtarget.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname},y | lda #0 | iny | sta ${wordtarget.asmVarname},y")
}
else -> {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | lda #0 | sta P8ESTACK_HI,x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda P8ESTACK_LO,x")
popAndWriteArrayvalueWithUnscaledIndexA(wordtarget.datatype, wordtarget.asmVarname)
}
}
}
TargetStorageKind.REGISTER -> {
when(wordtarget.register!!) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
else -> throw AssemblyError("only reg pairs are words")
}
}
TargetStorageKind.STACK -> {
asmgen.out("""
lda #$sourceName
sta P8ESTACK_LO,x
lda #0
sta P8ESTACK_HI,x
dex""")
}
else -> throw AssemblyError("other types aren't word")
}
}
private fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
require(target.datatype in ByteDatatypes)
when(target.kind) {
@ -569,19 +652,25 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.A -> {}
RegisterOrPair.X -> { asmgen.out(" tax") }
RegisterOrPair.Y -> { asmgen.out(" tay") }
else -> throw AssemblyError("attempt to assign byte to register pair word")
RegisterOrPair.AY -> { asmgen.out(" ldy #0") }
RegisterOrPair.AX -> { asmgen.out(" ldx #0") }
RegisterOrPair.XY -> { asmgen.out(" tax | ldy #0") }
}
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")
RegisterOrPair.AY -> { asmgen.out(" txa | ldy #0") }
RegisterOrPair.AX -> { asmgen.out(" txa | ldx #0") }
RegisterOrPair.XY -> { asmgen.out(" ldy #0") }
}
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")
RegisterOrPair.AY -> { asmgen.out(" tya | ldy #0") }
RegisterOrPair.AX -> { asmgen.out(" tya | ldx #0") }
RegisterOrPair.XY -> { asmgen.out(" tya | tax | ldy #0") }
}
}
}
@ -690,7 +779,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
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")
RegisterOrPair.AX -> asmgen.out(" lda #${byte.toHex()} | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda #${byte.toHex()} | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx #${byte.toHex()} | ldy #0")
}
TargetStorageKind.STACK -> {
asmgen.out("""
@ -706,7 +797,16 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// optimized case for float zero
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
if(CompilationTarget.instance.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
@ -740,7 +840,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta P8ZP_SCRATCH_W1
lda #>${target.asmVarname}
sta P8ZP_SCRATCH_W1+1
jsr c64flt.set_0_array_float
jsr floats.set_0_array_float
""")
}
}
@ -748,7 +848,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
TargetStorageKind.STACK -> {
val floatConst = asmgen.getFloatAsmConst(float)
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
}
}
} else {
@ -803,7 +903,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta P8ZP_SCRATCH_W2
lda #>${arrayVarName}
sta P8ZP_SCRATCH_W2+1
jsr c64flt.set_array_float
jsr floats.set_array_float
""")
}
}
@ -811,7 +911,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.REGISTER -> throw AssemblyError("can't assign float to register")
TargetStorageKind.STACK -> {
val floatConst = asmgen.getFloatAsmConst(float)
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
}
}
}
@ -836,7 +936,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
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")
RegisterOrPair.AX -> asmgen.out(" lda ${address.toHex()} | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda ${address.toHex()} | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldy ${address.toHex()} | ldy #0")
}
TargetStorageKind.STACK -> {
asmgen.out("""
@ -864,7 +966,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.A -> {}
RegisterOrPair.X -> asmgen.out(" tax")
RegisterOrPair.Y -> asmgen.out(" tay")
else -> throw AssemblyError("can't assign byte to word register apir")
RegisterOrPair.AX -> asmgen.out(" ldx #0")
RegisterOrPair.AY -> asmgen.out(" ldy #0")
RegisterOrPair.XY -> asmgen.out(" tax | ldy #0")
}
}
TargetStorageKind.STACK -> {
@ -875,6 +979,63 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignMemoryByteIntoWord(wordtarget: AsmAssignTarget, address: Int?, identifier: IdentifierReference?) {
if (address != null) {
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda ${address.toHex()}
sta ${wordtarget.asmVarname}
lda #0
sta ${wordtarget.asmVarname}+1
""")
}
TargetStorageKind.ARRAY -> {
throw AssemblyError("no asm gen for assign memory byte at $address to array ${wordtarget.asmVarname}")
}
TargetStorageKind.REGISTER -> when(wordtarget.register!!) {
RegisterOrPair.AX -> asmgen.out(" lda ${address.toHex()} | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda ${address.toHex()} | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldy ${address.toHex()} | ldy #0")
else -> throw AssemblyError("word regs can only be pair")
}
TargetStorageKind.STACK -> {
asmgen.out("""
lda ${address.toHex()}
sta P8ESTACK_LO,x
lda #0
sta P8ESTACK_HI,x
dex""")
}
else -> throw AssemblyError("other types aren't word")
}
} else if (identifier != null) {
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.loadByteFromPointerIntoA(identifier)
asmgen.out(" sta ${wordtarget.asmVarname} | lda #0 | sta ${wordtarget.asmVarname}+1")
}
TargetStorageKind.ARRAY -> {
throw AssemblyError("no asm gen for assign memory byte $identifier to array ${wordtarget.asmVarname} ")
}
TargetStorageKind.REGISTER -> {
asmgen.loadByteFromPointerIntoA(identifier)
when(wordtarget.register!!) {
RegisterOrPair.AX -> asmgen.out(" ldx #0")
RegisterOrPair.AY -> asmgen.out(" ldy #0")
RegisterOrPair.XY -> asmgen.out(" tax | ldy #0")
else -> throw AssemblyError("word regs can only be pair")
}
}
TargetStorageKind.STACK -> {
asmgen.loadByteFromPointerIntoA(identifier)
asmgen.out(" sta P8ESTACK_LO,x | lda #0 | sta P8ESTACK_HI,x | dex")
}
else -> throw AssemblyError("other types aren't word")
}
}
}
private fun storeByteViaRegisterAInMemoryAddress(ldaInstructionArg: String, memoryAddress: DirectMemoryWrite) {
val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
@ -946,7 +1107,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
dex
lda #<$asmArrayvarname
ldy #>$asmArrayvarname
jsr c64flt.pop_float_to_indexed_var
jsr floats.pop_float_to_indexed_var
""")
else ->
throw AssemblyError("weird array type")

View File

@ -5,11 +5,15 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
import prog8.compiler.toHex
import kotlin.math.absoluteValue
internal class AugmentableAssignmentAsmGen(private val program: Program,
private val assignmentAsmGen: AssignmentAsmGen,
private val exprAsmGen: ExpressionsAsmGen,
private val asmgen: AsmGen) {
fun translate(assign: AsmAssignment) {
require(assign.isAugmentable)
@ -21,8 +25,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// A = -A , A = +A, A = ~A, A = not A
val type = value.inferType(program).typeOrElse(DataType.STRUCT)
when (value.operator) {
"+" -> {
}
"+" -> {}
"-" -> inplaceNegate(assign.target, type)
"~" -> inplaceInvert(assign.target, type)
"not" -> inplaceBooleanNot(assign.target, type)
@ -103,6 +106,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification(target: AsmAssignTarget, operator: String, value: Expression) {
val valueLv = (value as? NumericLiteralValue)?.number
val ident = value as? IdentifierReference
val memread = value as? DirectMemoryRead
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -111,7 +115,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when {
valueLv != null -> inplaceModification_byte_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
// TODO more specialized code for types such as memory read etc.
memread != null -> inplaceModification_byte_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
@ -123,19 +127,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when {
valueLv != null -> inplaceModification_word_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt())
ident != null -> inplaceModification_word_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
// TODO more specialized code for types such as memory read etc.
// value is DirectMemoryRead -> {
// println("warning: slow stack evaluation used (8): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
// // assignmentAsmGen.translateOtherAssignment(origAssign)
// asmgen.translateExpression(value.addressExpression)
// asmgen.out("""
// jsr prog8_lib.read_byte_from_address_on_stack
// sta ...
// inx
// """)
// inplaceModification_word_value_to_variable(name, operator, )
// // TODO
// }
memread != null -> inplaceModification_word_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
@ -147,7 +139,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when {
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble())
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident)
// TODO more specialized code for types such as memory read etc.
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(target.asmVarname, operator, value)
@ -191,13 +182,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else -> {
println("warning: slow stack evaluation used (1): ${memory.addressExpression::class.simpleName} at ${memory.addressExpression.position}") // TODO optimize...
asmgen.translateExpression(memory.addressExpression)
// TODO buggy?:
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
val zp = CompilationTarget.machine.zeropage
val zp = CompilationTarget.instance.machine.zeropage
when {
valueLv != null -> inplaceModification_byte_litval_to_variable(zp.SCRATCH_B1.toHex(), DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable(zp.SCRATCH_B1.toHex(), DataType.UBYTE, operator, ident)
// TODO more specialized code for types such as memory read etc.
memread != null -> inplaceModification_byte_memread_to_variable(zp.SCRATCH_B1.toHex(), DataType.UBYTE, operator, memread)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(zp.SCRATCH_B1.toHex(), DataType.UBYTE, operator, value)
@ -250,16 +240,18 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"*" -> TODO("mul")// asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
"/" -> TODO("div")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"*" -> {
TODO("mul mem byte")// asmgen.out(" jsr prog8_lib.mul_byte")
}
"/" -> TODO("div mem byte")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"%" -> {
TODO("byte remainder")
TODO("mem byte remainder")
// if(types==DataType.BYTE)
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
// asmgen.out(" jsr prog8_lib.remainder_ub")
}
"<<" -> TODO("ubyte asl")
">>" -> TODO("ubyte lsr")
"<<" -> TODO("mem ubyte asl")
">>" -> TODO("mem ubyte lsr")
"&" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" and P8ESTACK_LO+1,x")
@ -309,16 +301,18 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"*" -> TODO("mul")// asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
"/" -> TODO("div")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"*" -> {
TODO("mem mul")// asmgen.out(" jsr prog8_lib.mul_byte")
}
"/" -> TODO("mem div")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"%" -> {
TODO("byte remainder")
TODO("mem byte remainder")
// if(types==DataType.BYTE)
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
// asmgen.out(" jsr prog8_lib.remainder_ub")
}
"<<" -> TODO("ubyte asl")
">>" -> TODO("ubyte lsr")
"<<" -> TODO("mem ubyte asl")
">>" -> TODO("mem ubyte lsr")
"&" -> {
val (ptrOnZp, sourceName) = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" and $otherName")
@ -367,19 +361,23 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
"*" -> {
TODO("mul byte litval")
// asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
if(value in asmgen.optimizedByteMultiplications) {
TODO("optimized mem mul ubyte litval $value")
} else {
TODO("mem mul ubyte litval $value")
// asmgen.out(" jsr prog8_lib.mul_byte")
}
}
"/" -> {
if(value==0)
throw AssemblyError("division by zero")
TODO("div byte litval")
TODO("mem div byte litval")
// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
}
"%" -> {
if(value==0)
throw AssemblyError("division by zero")
TODO("byte remainder litval")
TODO("mem byte remainder litval")
// if(types==DataType.BYTE)
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
// asmgen.out(" jsr prog8_lib.remainder_ub")
@ -442,14 +440,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"+" -> asmgen.out(" lda $name | clc | adc P8ESTACK_LO+1,x | sta $name")
"-" -> asmgen.out(" lda $name | sec | sbc P8ESTACK_LO+1,x | sta $name")
"*" -> {
TODO("mul byte expr")
// asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
TODO("var mul byte expr")
// check optimizedByteMultiplications
// asmgen.out(" jsr prog8_lib.mul_byte")
}
"/" -> {
TODO("div byte expr")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
TODO("var div byte expr")// asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
}
"%" -> {
TODO("byte remainder expr")
TODO("var byte remainder expr")
// if(types==DataType.BYTE)
// throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
// asmgen.out(" jsr prog8_lib.remainder_ub")
@ -506,7 +505,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"*" -> asmgen.out(" lda $name | ldy $otherName | jsr math.multiply_bytes | sta $name")
"/" -> {
if(dt==DataType.BYTE) {
TODO("signed byte divide see prog8lib.idiv_b")
asmgen.out(" lda $name | ldy $otherName | jsr math.divmod_b_asm | sty $name")
}
else {
asmgen.out(" lda $name | ldy $otherName | jsr math.divmod_ub_asm | sty $name")
@ -572,9 +571,21 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"+" -> asmgen.out(" lda $name | clc | adc #$value | sta $name")
"-" -> asmgen.out(" lda $name | sec | sbc #$value | sta $name")
"*" -> {
// TODO what about the optimized mul_5 etc routines?
TODO("byte mul litval")
// asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
if(dt == DataType.UBYTE) {
if(value in asmgen.optimizedByteMultiplications) {
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
} else {
TODO("var mul ubyte litval $value")
// asmgen.out(" jsr prog8_lib.mul_byte")
}
} else {
if(value.absoluteValue in asmgen.optimizedByteMultiplications) {
asmgen.out(" lda $name | jsr math.mul_byte_$value | sta $name")
} else {
TODO("var mul sbyte litval $value")
// asmgen.out(" jsr prog8_lib.mul_byte")
}
}
}
"/" -> {
if (dt == DataType.UBYTE) {
@ -585,7 +596,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sty $name
""")
} else {
TODO("BYTE div litval")
TODO("var BYTE div litval")
}
}
"%" -> {
@ -616,20 +627,77 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
when(operator) {
"+" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out("""
clc
adc $name
sta $name""")
}
"-" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out("""
sta P8ZP_SCRATCH_B1
lda $name
sec
sbc P8ZP_SCRATCH_B1
sta $name""")
// TODO: more operators
}
else -> {
inplaceModification_byte_value_to_variable(name, dt, operator, memread);
}
}
}
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
when(operator) {
"+" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out("""
clc
adc $name
sta $name
bcc +
inc $name+1
+""")
}
"-" -> {
exprAsmGen.translateDirectMemReadExpression(memread, false)
asmgen.out("""
sta P8ZP_SCRATCH_B1
lda $name
sec
sbc P8ZP_SCRATCH_B1
sta $name
bcc +
dec $name+1
+""")
// TODO: more operators
}
else -> {
inplaceModification_word_value_to_variable(name, dt, operator, memread);
}
}
}
private fun inplaceModification_word_litval_to_variable(name: String, dt: DataType, operator: String, value: Int) {
when (operator) {
// note: ** (power) operator requires floats.
// TODO use these + and - optimizations in the expressionAsmGenerator as well.
"+" -> {
when {
value<0x0100 -> asmgen.out("""
value==0 -> {}
value in 1..0xff -> asmgen.out("""
lda $name
clc
adc #$value
sta $name
bcc +
inc $name+1
+ """)
+""")
value==0x0100 -> asmgen.out(" inc $name+1")
value==0x0200 -> asmgen.out(" inc $name+1 | inc $name+1")
value==0x0300 -> asmgen.out(" inc $name+1 | inc $name+1 | inc $name+1")
@ -646,14 +714,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"-" -> {
when {
value<0x0100 -> asmgen.out("""
value==0 -> {}
value in 1..0xff -> asmgen.out("""
lda $name
sec
sbc #$value
sta $name
bcs +
dec $name+1
+ """)
+""")
value==0x0100 -> asmgen.out(" dec $name+1")
value==0x0200 -> asmgen.out(" dec $name+1 | dec $name+1")
value==0x0300 -> asmgen.out(" dec $name+1 | dec $name+1 | dec $name+1")
@ -669,25 +738,58 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
"*" -> {
// TODO what about the optimized mul_5 etc routines?
asmgen.out("""
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
if(dt == DataType.UWORD){
if(value in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
} else {
asmgen.out("""
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
}
} else {
if(value.absoluteValue in asmgen.optimizedWordMultiplications) {
asmgen.out(" lda $name | ldy $name+1 | jsr math.mul_word_$value | sta $name | sty $name+1")
} else {
// TODO does this work for signed words? if so the uword/word distinction can be removed altogether
asmgen.out("""
lda $name
sta P8ZP_SCRATCH_W1
lda $name+1
sta P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
}
}
}
"/" -> {
if(value==0)
throw AssemblyError("division by zero")
if(dt==DataType.WORD) {
TODO("signed word divide see prog8lib.idiv_w")
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<$value
ldy #>$value
jsr math.divmod_w_asm
sta $name
sty $name+1
""")
}
else {
asmgen.out("""
@ -736,9 +838,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"&" -> {
when {
value == 0 -> asmgen.out(" lda #0 | sta $name | sta $name+1")
value and 255 == 0 -> asmgen.out(" lda #0 | sta $name | lda $name+1 | and #>$value | sta $name+1")
value < 0x0100 -> asmgen.out(" lda $name | and #$value | sta $name | lda #0 | sta $name+1")
value == 0 -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" stz $name | stz $name+1")
else
asmgen.out(" lda #0 | sta $name | sta $name+1")
}
value and 255 == 0 -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
asmgen.out(" lda $name+1 | and #>$value | sta $name+1")
}
value < 0x0100 -> {
asmgen.out(" lda $name | and #$value | sta $name")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
}
else -> asmgen.out(" lda $name | and #<$value | sta $name | lda $name+1 | and #>$value | sta $name+1")
}
}
@ -767,28 +886,73 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
val valueDt = ident.targetVarDecl(program.namespace)!!.datatype
when (valueDt) {
in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that TODO does this even occur?
// the other variable is a BYTE type so optimize for that
when (operator) {
// note: ** (power) operator requires floats.
"+" -> asmgen.out("""
lda $name
clc
adc $otherName
sta $name
bcc +
inc $name+1
+ """)
"-" -> asmgen.out("""
lda $name
sec
sbc $otherName
sta $name
bcs +
dec $name+1
+ """)
"*" -> TODO("mul word*byte")
"/" -> TODO("div word/byte")
"%" -> TODO("word remainder byte")
"+" -> {
if(valueDt==DataType.UBYTE)
asmgen.out("""
lda $name
clc
adc $otherName
sta $name
bcc +
inc $name+1
+""")
else
asmgen.out("""
ldy #0
lda $otherName
bpl +
dey ; sign extend
+ clc
adc $name
sta $name
tya
adc $name+1
sta $name+1""")
}
"-" -> {
if(valueDt==DataType.UBYTE)
asmgen.out("""
lda $name
sec
sbc $otherName
sta $name
bcs +
dec $name+1
+""")
else
asmgen.out("""
ldy #0
lda $otherName
bpl +
dey ; sign extend
+ sty P8ZP_SCRATCH_B1
lda $name
sec
sbc $otherName
sta $name
lda $name+1
sbc P8ZP_SCRATCH_B1
sta $name+1""")
}
"*" -> {
asmgen.out("""
lda $otherName
sta P8ZP_SCRATCH_W1
lda #0
sta P8ZP_SCRATCH_W1+1
lda $name
ldy $name+1
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
}
"/" -> TODO("div (u)wordvar/bytevar")
"%" -> TODO("(u)word remainder bytevar")
"<<" -> {
asmgen.out("""
ldy $otherName
@ -816,9 +980,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
bne -""")
}
}
"&" -> TODO("bitand word byte")
"^" -> TODO("bitxor word byte")
"|" -> TODO("bitor word byte")
"&" -> TODO("bitand (u)wordvar bytevar")
"^" -> TODO("bitxor (u)wordvar bytevar")
"|" -> TODO("bitor (u)wordvar bytevar")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -845,7 +1009,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"/" -> {
if(dt==DataType.WORD) {
TODO("signed word divide see prog8lib.idiv_w")
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $otherName
ldy $otherName+1
jsr math.divmod_w_asm
sta $name
sty $name+1
""")
}
else {
asmgen.out("""
@ -900,28 +1074,60 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when(valueDt) {
in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that TODO does this even occur?
// the other variable is a BYTE type so optimize for that
when (operator) {
// note: ** (power) operator requires floats.
"+" -> asmgen.out("""
lda $name
clc
adc P8ESTACK_LO+1,x
sta $name
bcc +
inc $name+1
+ """)
"-" -> asmgen.out("""
lda $name
sec
sbc P8ESTACK_LO+1,x
sta $name
bcs +
dec $name+1
+ """)
"*" -> TODO("mul word byte")
"/" -> TODO("div word byte")
"%" -> TODO("word remainder byte")
"+" -> {
if(valueDt==DataType.UBYTE)
asmgen.out("""
lda $name
clc
adc P8ESTACK_LO+1,x
sta $name
bcc +
inc $name+1
+""")
else
asmgen.out("""
ldy #0
lda P8ESTACK_LO+1,x
bpl +
dey ; sign extend
+ clc
adc $name
sta $name
tya
adc $name+1
sta $name+1""")
}
"-" -> {
if(valueDt==DataType.UBYTE)
asmgen.out("""
lda $name
sec
sbc P8ESTACK_LO+1,x
sta $name
bcc +
dec $name+1
+""")
else
asmgen.out("""
ldy #0
lda P8ESTACK_LO+1,x
bpl +
dey ; sign extend
+ sty P8ZP_SCRATCH_B1
lda $name
sec
sbc P8ESTACK_LO+1,x
sta $name
lda $name+1
sbc P8ZP_SCRATCH_B1
sta $name+1""")
}
"*" -> TODO("mul (u)word (u)byte")
"/" -> TODO("div (u)word (u)byte")
"%" -> TODO("(u)word remainder (u)byte")
"<<" -> {
asmgen.translateExpression(value)
asmgen.out("""
@ -960,9 +1166,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""")
}
}
"&" -> TODO("bitand word byte")
"^" -> TODO("bitxor word byte")
"|" -> TODO("bitor word byte")
"&" -> TODO("bitand (u)word (u)byte")
"^" -> TODO("bitxor (u)word (u)byte")
"|" -> TODO("bitor (u)word (u)byte")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -989,7 +1195,17 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"/" -> {
if (dt == DataType.WORD) {
TODO("signed word divide see prog8lib.idiv_w")
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda P8ESTACK_LO+1,x
ldy P8ESTACK_HI+1,x
jsr math.divmod_w_asm
sta $name
sty $name+1
""")
} else {
asmgen.out("""
lda $name
@ -1041,62 +1257,53 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// because the value is evaluated onto the eval stack (=slow).
println("warning: slow stack evaluation used (2): $name $operator= ${value::class.simpleName} at ${value.position}") // TODO
asmgen.translateExpression(value)
asmgen.out(" jsr floats.pop_float_fac1")
asmgen.saveRegister(CpuRegister.X)
when (operator) {
"**" -> {
asmgen.out("""
jsr c64flt.pop_float_fac1
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.CONUPK
jsr c64flt.FPWRT
jsr floats.CONUPK
jsr floats.FPWRT
""")
}
"+" -> {
asmgen.out("""
jsr c64flt.pop_float_fac1
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.FADD
jsr floats.FADD
""")
}
"-" -> {
asmgen.out("""
jsr c64flt.pop_float_fac1
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.FSUB
jsr floats.FSUB
""")
}
"*" -> {
asmgen.out("""
jsr c64flt.pop_float_fac1
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.FMULT
jsr floats.FMULT
""")
}
"/" -> {
asmgen.out("""
jsr c64flt.pop_float_fac1
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.FDIV
jsr floats.FDIV
""")
}
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
}
// store Fac1 back into memory
asmgen.out("""
ldx #<$name
ldy #>$name
jsr c64flt.MOVMF
ldx P8ZP_SCRATCH_REG_X
jsr floats.MOVMF
""")
asmgen.restoreRegister(CpuRegister.X)
}
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference) {
@ -1105,60 +1312,56 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
throw AssemblyError("float variable expected")
val otherName = asmgen.asmVariableName(ident)
asmgen.saveRegister(CpuRegister.X)
when (operator) {
"**" -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.CONUPK
jsr floats.CONUPK
lda #<$otherName
ldy #>$otherName
jsr c64flt.FPWR
jsr floats.FPWR
""")
}
"+" -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.MOVFM
jsr floats.MOVFM
lda #<$otherName
ldy #>$otherName
jsr c64flt.FADD
jsr floats.FADD
""")
}
"-" -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<$otherName
ldy #>$otherName
jsr c64flt.MOVFM
jsr floats.MOVFM
lda #<$name
ldy #>$name
jsr c64flt.FSUB
jsr floats.FSUB
""")
}
"*" -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.MOVFM
jsr floats.MOVFM
lda #<$otherName
ldy #>$otherName
jsr c64flt.FMULT
jsr floats.FMULT
""")
}
"/" -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<$otherName
ldy #>$otherName
jsr c64flt.MOVFM
jsr floats.MOVFM
lda #<$name
ldy #>$name
jsr c64flt.FDIV
jsr floats.FDIV
""")
}
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
@ -1167,73 +1370,70 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out("""
ldx #<$name
ldy #>$name
jsr c64flt.MOVMF
ldx P8ZP_SCRATCH_REG_X
jsr floats.MOVMF
""")
asmgen.restoreRegister(CpuRegister.X)
}
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double) {
val constValueName = asmgen.getFloatAsmConst(value)
asmgen.saveRegister(CpuRegister.X)
when (operator) {
"**" -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.CONUPK
jsr floats.CONUPK
lda #<$constValueName
ldy #>$constValueName
jsr c64flt.FPWR
jsr floats.FPWR
""")
}
"+" -> {
if (value == 0.0)
return
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.MOVFM
jsr floats.MOVFM
lda #<$constValueName
ldy #>$constValueName
jsr c64flt.FADD
jsr floats.FADD
""")
}
"-" -> {
if (value == 0.0)
return
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<$constValueName
ldy #>$constValueName
jsr c64flt.MOVFM
jsr floats.MOVFM
lda #<$name
ldy #>$name
jsr c64flt.FSUB
jsr floats.FSUB
""")
}
"*" -> {
// assume that code optimization is already done on the AST level for special cases such as 0, 1, 2...
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<$name
ldy #>$name
jsr c64flt.MOVFM
jsr floats.MOVFM
lda #<$constValueName
ldy #>$constValueName
jsr c64flt.FMULT
jsr floats.FMULT
""")
}
"/" -> {
if (value == 0.0)
throw AssemblyError("division by zero")
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<$constValueName
ldy #>$constValueName
jsr c64flt.MOVFM
jsr floats.MOVFM
lda #<$name
ldy #>$name
jsr c64flt.FDIV
jsr floats.FDIV
""")
}
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
@ -1242,15 +1442,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out("""
ldx #<$name
ldy #>$name
jsr c64flt.MOVMF
ldx P8ZP_SCRATCH_REG_X
jsr floats.MOVMF
""")
asmgen.restoreRegister(CpuRegister.X)
}
private fun inplaceCast(target: AsmAssignTarget, cast: TypecastExpression, position: Position) {
val outerCastDt = cast.type
val innerCastDt = (cast.expression as? TypecastExpression)?.type
if (innerCastDt == null) {
// simple typecast where the value is the target
when (target.datatype) {
@ -1259,12 +1458,25 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when (outerCastDt) {
DataType.UBYTE, DataType.BYTE -> {
when(target.kind) {
TargetStorageKind.VARIABLE -> asmgen.out(" lda #0 | sta ${target.asmVarname}+1")
TargetStorageKind.VARIABLE -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" stz ${target.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${target.asmVarname}+1")
}
TargetStorageKind.ARRAY -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, target.datatype, CpuRegister.Y, true)
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" stz ${target.asmVarname},y")
else
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
}
TargetStorageKind.STACK -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" stz P8ESTACK_HI+1,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
}
TargetStorageKind.STACK -> asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
else -> throw AssemblyError("weird target")
}
}
@ -1468,16 +1680,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
DataType.FLOAT -> {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
// simply flip the sign bit in the float
asmgen.out("""
stx P8ZP_SCRATCH_REG_X
lda #<${target.asmVarname}
ldy #>${target.asmVarname}
jsr c64flt.MOVFM
jsr c64flt.NEGOP
ldx #<${target.asmVarname}
ldy #>${target.asmVarname}
jsr c64flt.MOVMF
ldx P8ZP_SCRATCH_REG_X
lda ${target.asmVarname}+1
eor #$80
sta ${target.asmVarname}+1
""")
}
TargetStorageKind.ARRAY -> TODO("in-place negate float array")

View File

@ -2,6 +2,7 @@ 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
@ -9,7 +10,7 @@ import java.io.IOException
internal object CX16MachineDefinition: IMachineDefinition {
override val cpu = "65c02"
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
@ -25,14 +26,13 @@ internal object CX16MachineDefinition: IMachineDefinition {
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 // TODO Does Cx16 have ROM float locations?
override fun getFloatRomConst(number: Double): String? = null // Cx16 has no pulblic ROM float locations
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "cx16lib")
importer.importLibraryModule(program, "syslib")
}
override fun launchEmulator(programName: String) {
@ -52,39 +52,34 @@ internal object CX16MachineDefinition: IMachineDefinition {
}
}
override fun isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
}
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
// TODO add 65C02 opcodes
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
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", "dcm", "dcp", "dec", "dex", "dey",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
"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'")
@ -94,16 +89,16 @@ internal object CX16MachineDefinition: IMachineDefinition {
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))
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, 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))
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, 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))
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
@ -113,7 +108,6 @@ internal object CX16MachineDefinition: IMachineDefinition {
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)

View File

@ -7,178 +7,6 @@ import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
// First thing to do is replace all constant identifiers with their actual value,
// and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
// replace identifiers that refer to const value, with the value itself
// if it's a simple type and if it's not a left hand side variable
if(identifier.parent is AssignTarget)
return noModifications
var forloop = identifier.parent as? ForLoop
if(forloop==null)
forloop = identifier.parent.parent as? ForLoop
if(forloop!=null && identifier===forloop.loopVar)
return noModifications
val cval = identifier.constValue(program) ?: return noModifications
return when (cval.type) {
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> noModifications
}
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call graph for this?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
if(decl.isArray){
if(decl.arraysize==null) {
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
val arrayval = decl.value as? ArrayLiteralValue
if(arrayval!=null) {
return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
decl
))
}
}
else if(decl.arraysize?.constIndex()==null) {
val size = decl.arraysize!!.index.constValue(program)
if(size!=null) {
return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
size, decl
))
}
}
}
when(decl.datatype) {
DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(numericLv!=null && numericLv.type==DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return noModifications
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
DataType.ARRAY_F -> {
val size = decl.arraysize?.constIndex() ?: return noModifications
val litval = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(rangeExpr==null && litval!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < CompilationTarget.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) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
}
else -> {
// nothing to do for this type
// this includes strings and structs
}
}
}
val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declValue.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
}
return noModifications
}
}
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
@ -216,9 +44,24 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
}
"~" -> when (subexpr.type) {
in IntegerDatatypes -> {
DataType.BYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalInteger(subexpr.number.toInt().inv(), subexpr.position),
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
parent))
}
DataType.UBYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UBYTE, subexpr.number.toInt().inv() and 255, subexpr.position),
parent))
}
DataType.WORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
parent))
}
DataType.UWORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UWORD, subexpr.number.toInt().inv() and 65535, subexpr.position),
parent))
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
@ -318,11 +161,11 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
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 range
return null
val newStep =
if(stepLiteral!=null) {
@ -351,28 +194,32 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
if(rangeFrom.type!= DataType.UBYTE) {
// attempt to translate the iterable into ubyte values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
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)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
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)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
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)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")

View File

@ -0,0 +1,192 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.compiler.target.CompilationTarget
// Fix up the literal value's type to match that of the vardecl
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& !declConstValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
}
return noModifications
}
}
// Replace all constant identifiers with their actual value,
// and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
// replace identifiers that refer to const value, with the value itself
// if it's a simple type and if it's not a left hand side variable
if(identifier.parent is AssignTarget)
return noModifications
var forloop = identifier.parent as? ForLoop
if(forloop==null)
forloop = identifier.parent.parent as? ForLoop
if(forloop!=null && identifier===forloop.loopVar)
return noModifications
val cval = identifier.constValue(program) ?: return noModifications
return when (cval.type) {
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> noModifications
}
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call graph for this?
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
if(decl.type== VarDeclType.CONST || decl.type== VarDeclType.VAR) {
if(decl.isArray){
if(decl.arraysize==null) {
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
val arrayval = decl.value as? ArrayLiteralValue
if(arrayval!=null) {
return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
decl
))
}
}
else if(decl.arraysize?.constIndex()==null) {
val size = decl.arraysize!!.index.constValue(program)
if(size!=null) {
return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
size, decl
))
}
}
}
when(decl.datatype) {
DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return noModifications
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
DataType.ARRAY_F -> {
val size = decl.arraysize?.constIndex() ?: return noModifications
val litval = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(rangeExpr==null && litval!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", litval.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
}
else -> {
// nothing to do for this type
// this includes strings and structs
}
}
}
return noModifications
}
}

View File

@ -1,11 +1,15 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.VarDecl
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
@ -175,28 +179,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
// unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
}
when(leftDt) {
DataType.BYTE -> {
// signed >=0 --> signed ^ $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
DataType.WORD -> {
// signedw >=0 --> msb(signedw) ^ $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
mutableListOf(expr.left),
expr.position
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
else -> {}
}
}
if(expr.operator == "<" && rightVal?.number == 0) {
@ -297,6 +279,123 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
return noModifications
}
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
// if(decl.type==VarDeclType.VAR ) {
// val binExpr = decl.value as? BinaryExpression
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
// val assign = Assignment(target, augExpr, binExpr.position)
// println("SPLIT VARDECL $decl")
// return listOf(
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
// IAstModification.InsertAfter(decl, assign, parent)
// )
// }
// }
// return noModifications
// }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
/*
reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps:
X = BinExpr X = LeftExpr
<operator> followed by
/ \ IF 'X' not used X = BinExpr
/ \ IN LEFTEXPR ==> <operator>
/ \ / \
LeftExpr. RightExpr. / \
/ \ / \ X RightExpr.
.. .. .. ..
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
if (!assignment.isAugmentable) {
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf(
IAstModification.InsertBefore(assignment, firstAssign, parent),
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
}
}
// TODO further unraveling of binary expression trees into flat statements.
// however this should probably be done in a more generic way to also service
// the expressiontrees that are not used in an assignment statement...
}
return noModifications
}
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope): Boolean {
return when {
target.identifier!=null -> target.isInRegularRAM(namespace)
target.memoryAddress!=null -> target.isInRegularRAM(namespace)
target.arrayindexed!=null -> {
val index = target.arrayindexed!!.arrayspec.index
if(index is NumericLiteralValue)
target.isInRegularRAM(namespace)
else
false
}
else -> false
}
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
if(functionCall.target.nameInSource == listOf("lsb")) {
val arg = functionCall.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
// useless lsb() of byte value that was casted to word
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
}
} else {
val argDt = arg.inferType(program)
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
// useless lsb() of byte value
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
}
}
}
else if(functionCall.target.nameInSource == listOf("msb")) {
val arg = functionCall.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
// useless msb() of byte value that was casted to word, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
NumericLiteralValue(valueDt.typeOrElse(DataType.UBYTE), 0, arg.expression.position),
parent))
}
} else {
val argDt = arg.inferType(program)
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
// useless msb() of byte value, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
NumericLiteralValue(argDt.typeOrElse(DataType.UBYTE), 0, arg.position),
parent))
}
}
}
return noModifications
}
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when {
subBinExpr.left isSameAs x -> subBinExpr.right

View File

@ -5,20 +5,31 @@ import prog8.ast.base.ErrorReporter
internal fun Program.constantFold(errors: ErrorReporter) {
val replacer = ConstantIdentifierReplacer(this, errors)
replacer.visit(this)
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this)
if(errors.isEmpty()) {
replacer.applyModifications()
valuetypefixer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this)
optimizer.visit(this)
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
}
if(errors.isEmpty()) {
replacer.visit(this)
val replacer = ConstantIdentifierReplacer(this, errors)
replacer.visit(this)
if (errors.isEmpty()) {
replacer.applyModifications()
valuetypefixer.visit(this)
if(errors.isEmpty()) {
valuetypefixer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this)
optimizer.visit(this)
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
}
if (errors.isEmpty()) {
replacer.visit(this)
replacer.applyModifications()
}
}
}
}

View File

@ -49,11 +49,6 @@ internal class StatementOptimizer(private val program: Program,
}
}
val linesToRemove = deduplicateAssignments(subroutine.statements)
if(linesToRemove.isNotEmpty()) {
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, parent))
@ -62,11 +57,6 @@ internal class StatementOptimizer(private val program: Program,
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val linesToRemove = deduplicateAssignments(scope.statements)
return linesToRemove.reversed().map { IAstModification.Remove(scope.statements[it], scope) }
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) {
@ -104,7 +94,7 @@ internal class StatementOptimizer(private val program: Program,
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
val firstCharEncoded = CompilationTarget.instance.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
@ -112,7 +102,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if (string.value.length == 2) {
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
val firstTwoCharsEncoded = CompilationTarget.instance.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
@ -220,7 +210,7 @@ internal class StatementOptimizer(private val program: Program,
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 character = CompilationTarget.instance.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))
@ -249,11 +239,11 @@ internal class StatementOptimizer(private val program: Program,
}
override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val constvalue = untilLoop.untilCondition.constValue(program)
val constvalue = untilLoop.condition.constValue(program)
if(constvalue!=null) {
if(constvalue.asBooleanValue) {
// always true -> keep only the statement block (if there are no break statements)
errors.warn("condition is always true", untilLoop.untilCondition.position)
errors.warn("condition is always true", untilLoop.condition.position)
if(!hasBreak(untilLoop.body))
return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
} else {
@ -395,21 +385,21 @@ internal class StatementOptimizer(private val program: Program,
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.number?.toDouble()
if (cv != null && assignment.target isSameAs bexpr.left) {
val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
if (rightCv != null && assignment.target isSameAs bexpr.left) {
// assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) {
"+" -> {
if (cv == 0.0) {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..4.0) {
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several INCs if it's not a memory address (inc on a memory mapped register doesn't work very well)
val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
repeat(rightCv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
@ -417,62 +407,38 @@ internal class StatementOptimizer(private val program: Program,
}
}
"-" -> {
if (cv == 0.0) {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..4.0) {
} else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several DECs if it's not a memory address (dec on a memory mapped register doesn't work very well)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
repeat(rightCv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
}
}
}
"*" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"/" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"**" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"|" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"^" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"<<" -> {
if (cv == 0.0)
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
}
">>" -> {
if (cv == 0.0)
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
}
}
}
}
return noModifications
}
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
// removes 'duplicate' assignments that assign the isSameAs target
val linesToRemove = mutableListOf<Int>()
var previousAssignmentLine: Int? = null
for (i in statements.indices) {
val stmt = statements[i] as? Assignment
if (stmt != null && stmt.value is NumericLiteralValue) {
if (previousAssignmentLine == null) {
previousAssignmentLine = i
continue
} else {
val prev = statements[previousAssignmentLine] as Assignment
if (prev.target.isSameAs(stmt.target, program)) {
// get rid of the previous assignment, if the target is not MEMORY
if (prev.target.isNotMemory(program.namespace))
linesToRemove.add(previousAssignmentLine)
}
previousAssignmentLine = i
}
} else
previousAssignmentLine = null
}
return linesToRemove
return noModifications
}
private fun hasBreak(scope: INameScope): Boolean {

View File

@ -8,7 +8,8 @@ import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
internal class UnusedCodeRemover(private val errors: ErrorReporter): AstWalker() {
internal class UnusedCodeRemover(private val program: Program, private val errors: ErrorReporter): AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program)
@ -66,4 +67,35 @@ internal class UnusedCodeRemover(private val errors: ErrorReporter): AstWalker()
else -> errors.warn("unreachable code", next.position)
}
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val removeDoubleAssignments = deduplicateAssignments(scope.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, scope) }
}
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
val removeDoubleAssignments = deduplicateAssignments(block.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
}
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
// removes 'duplicate' assignments that assign the same target directly after another
val linesToRemove = mutableListOf<Assignment>()
for (stmtPairs in statements.windowed(2, step = 1)) {
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace))
linesToRemove.add(assign1)
}
}
return linesToRemove
}
}

View File

@ -9,6 +9,7 @@ import prog8.ast.base.SyntaxError
import prog8.ast.base.checkImportedValid
import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg
import prog8.compiler.target.CompilationTarget
import prog8.pathFrom
import java.io.InputStream
import java.nio.file.Files
@ -19,14 +20,6 @@ import java.nio.file.Paths
internal class ParsingFailedError(override var message: String) : Exception(message)
private class LexerErrorListener: BaseErrorListener() {
var numberOfErrors: Int = 0
override fun syntaxError(p0: Recognizer<*, *>?, p1: Any?, p2: Int, p3: Int, p4: String?, p5: RecognitionException?) {
numberOfErrors++
}
}
internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
@ -60,13 +53,28 @@ internal class ModuleImporter {
return executeImportDirective(program, import, Paths.get(""))
}
private class MyErrorListener: ConsoleErrorListener() {
var numberOfErrors: Int = 0
override fun syntaxError(recognizer: Recognizer<*, *>?, offendingSymbol: Any?, line: Int, charPositionInLine: Int, msg: String, e: RecognitionException?) {
numberOfErrors++
when (recognizer) {
is CustomLexer -> System.err.println("${recognizer.modulePath}:$line:$charPositionInLine: $msg")
is prog8Parser -> System.err.println("${recognizer.inputStream.sourceName}:$line:$charPositionInLine: $msg")
else -> System.err.println("$line:$charPositionInLine $msg")
}
}
}
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener()
lexer.removeErrorListeners()
val lexerErrors = MyErrorListener()
lexer.addErrorListener(lexerErrors)
val tokens = CommentHandlingTokenStream(lexer)
val parser = prog8Parser(tokens)
parser.removeErrorListeners()
parser.addErrorListener(MyErrorListener())
val parseTree = parser.module()
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
if(numberOfErrors > 0)
@ -123,13 +131,14 @@ internal class ModuleImporter {
if(existing!=null)
return null
val resource = tryGetEmbeddedResource("$moduleName.p8")
val rsc = tryGetEmbeddedResource("$moduleName.p8")
val importedModule =
if(resource!=null) {
if(rsc!=null) {
// load the module from the embedded resource
val (resource, resourcePath) = rsc
resource.use {
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"), true)
}
} else {
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
@ -140,7 +149,18 @@ internal class ModuleImporter {
return importedModule
}
private fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
private fun tryGetEmbeddedResource(name: String): Pair<InputStream, String>? {
val target = CompilationTarget.instance.name
val targetSpecificPath = "/prog8lib/$target/$name"
val targetSpecificResource = object{}.javaClass.getResourceAsStream(targetSpecificPath)
if(targetSpecificResource!=null)
return Pair(targetSpecificResource, targetSpecificPath)
val generalPath = "/prog8lib/$name"
val generalResource = object{}.javaClass.getResourceAsStream(generalPath)
if(generalResource!=null)
return Pair(generalResource, generalPath)
return null
}
}

View File

@ -5,18 +5,21 @@ import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii
import java.io.CharConversionException
import java.nio.file.Path
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -129,7 +132,7 @@ class TestC64Zeropage {
@Test
fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, errors)
@ -142,37 +145,37 @@ class TestC64Zeropage {
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
zp3.allocate("", DataType.FLOAT, null, errors)
}
@Test
fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false))
}
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false))
}
}
@Test
fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false))
println(zp.free)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
@ -182,19 +185,19 @@ class TestC64Zeropage {
@Test
fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
assertEquals(91, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
assertEquals(89, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertEquals(238, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertEquals(238, zp1.available())
assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free)
@ -203,7 +206,7 @@ class TestC64Zeropage {
assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false))
assertEquals(139, zp2.available())
assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free)
@ -216,7 +219,7 @@ class TestC64Zeropage {
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp.available())
assertFailsWith<ZeropageDepletedError> {
@ -239,7 +242,7 @@ class TestC64Zeropage {
@Test
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3)
@ -269,7 +272,7 @@ class TestC64Zeropage {
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
@ -379,3 +382,169 @@ class TestPetscii {
assertFalse(abc!=abc)
}
}
class TestMemory {
@Test
fun testInValidRamC64_memory_addresses() {
CompilationTarget.instance = C64Target
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
}
@Test
fun testNotInValidRamC64_memory_addresses() {
CompilationTarget.instance = C64Target
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
}
@Test
fun testInValidRamC64_memory_identifiers() {
CompilationTarget.instance = C64Target
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
assertTrue(target.isInRegularRAM(target.definingScope()))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
assertFalse(target.isInRegularRAM(target.definingScope()))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
assertTrue(target.isInRegularRAM(target.definingScope()))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
assertFalse(target.isInRegularRAM(target.definingScope()))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
assertFalse(target.isInRegularRAM(target.definingScope()))
}
@Test
private fun createTestProgramForMemoryRefViaVar(address: Int, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
return target
}
@Test
fun testInValidRamC64_memory_expression() {
CompilationTarget.instance = C64Target
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
}
@Test
fun testInValidRamC64_variable() {
CompilationTarget.instance = C64Target
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@Test
fun testInValidRamC64_memmap_variable() {
CompilationTarget.instance = C64Target
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@Test
fun testNotInValidRamC64_memmap_variable() {
CompilationTarget.instance = C64Target
val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertFalse(target.isInRegularRAM(target.definingScope()))
}
@Test
fun testInValidRamC64_array() {
CompilationTarget.instance = C64Target
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@Test
fun testInValidRamC64_array_memmapped() {
CompilationTarget.instance = C64Target
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@Test
fun testNotValidRamC64_array_memmapped() {
CompilationTarget.instance = C64Target
val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertFalse(target.isInRegularRAM(target.definingScope()))
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -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.
@ -149,10 +149,10 @@ If your running program hits one of the breakpoints, Vice will halt execution an
Troubleshooting
---------------
Getting an assembler error about undefined symbols such as ``not defined 'c64flt'``?
This happens when your program uses floating point values, and you forgot to import ``c64flt`` library.
Getting an assembler error about undefined symbols such as ``not defined 'floats'``?
This happens when your program uses floating point values, and you forgot to import ``floats`` library.
If you use floating points, the compiler needs routines from that library.
Fix it by adding an ``%import c64flt``.
Fix it by adding an ``%import floats``.
Examples

View File

@ -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>`_.
@ -37,21 +38,22 @@ This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/license
:alt: Fully playable tetris clone
Code examples
-------------
Code example
------------
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64textio
%import textio
%zeropage basicsafe
main {
ubyte[256] sieve
ubyte candidate_prime = 2
ubyte candidate_prime = 2 ; is increased in the loop
sub start() {
memset(sieve, 256, false) ; clear the sieve
; clear the sieve, to reset starting situation on subsequent runs
memset(sieve, 256, false)
; calculate primes
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0
repeat {
@ -62,18 +64,19 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
txt.print(", ")
amount++
}
c64.CHROUT('\n')
txt.chrout('\n')
txt.print("number of primes (expected 54): ")
txt.print_ub(amount)
c64.CHROUT('\n')
txt.chrout('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0 ; we wrapped; no more primes available
return 0 ; we wrapped; no more primes available in the sieve
}
; found next one, mark the multiples and return it.
sieve[candidate_prime] = true
uword multiple = candidate_prime
@ -87,7 +90,6 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
}
when compiled an ran on a C-64 you get this:
.. image:: _static/primes_example.png
@ -95,44 +97,6 @@ when compiled an ran on a C-64 you get this:
:alt: result when run on C-64
// TODO fix code example
The following programs shows a use of the high level ``struct`` type::
%import c64textio
%zeropage basicsafe
main {
struct Color {
ubyte red
ubyte green
ubyte blue
}
sub start() {
Color purple = [255, 0, 255]
Color other
other = purple
other.red /= 2
other.green = 10 + other.green / 2
other.blue = 99
txt.print_ub(other.red)
c64.CHROUT(',')
txt.print_ub(other.green)
c64.CHROUT(',')
txt.print_ub(other.blue)
c64.CHROUT('\n')
}
}
when compiled and ran, it prints ``127,10,99`` on the screen.
Design principles and features
------------------------------
@ -162,6 +126,7 @@ Design principles and features
the ability to easily write embedded assembly code directly in the program source code.
- There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``substr``, ``sort`` and ``reverse`` (and others)
- Assembling the generated code into a program wil be done by an external cross-assembler tool.
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
.. _requirements:
@ -179,6 +144,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'*)

View File

@ -226,7 +226,7 @@ This is because routines in the C-64 BASIC and KERNAL ROMs are used for that.
So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM)
are banked in.
Also your code needs to import the ``c64flt`` library to enable floating point support
Also your code needs to import the ``floats`` library to enable floating point support
in the compiler, and to gain access to the floating point routines.
(this library contains the directive to enable floating points, you don't have
to worry about this yourself)
@ -236,12 +236,15 @@ The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (ne
Arrays
^^^^^^
Array types are also supported. They can be made of bytes, words or floats::
Array types are also supported. They can be made of bytes, words or floats, strings, and other arrays
(although the usefulness of the latter is very limited for now)::
byte[10] array ; array of 10 bytes, initially set to 0
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to uword)
uword[] others = [names, array] ; array of pointers/addresses to other arrays
value = array[3] ; the fourth value in the array (index is 0-based)
char = string[4] ; the fifth character (=byte) in the string
@ -293,6 +296,12 @@ large enough to contain the new value::
string1 = "new value"
.. info::
Strings and uwords (=memory address) can often be interchanged.
An array of strings is actually an array of uwords where every element is the memory
address of the string. You can pass a memory address to assembly functions
that require a string as an argument.
.. caution::
It's probably best to avoid changing strings after they've been created. This
includes changing certain letters by index, or by assigning a new value, or by
@ -327,7 +336,7 @@ and then create a variable with it::
ubyte blue
}
Color rgb = {255,122,0} ; note the curly braces here instead of brackets
Color rgb = [255,122,0] ; note that struct initializer value is same as an array
Color another ; the init value is optional, like arrays
another = rgb ; assign all of the values of rgb to another
@ -578,25 +587,24 @@ within parentheses will be evaluated first. So ``(4 + 8) * 2`` is 24 and not 20,
and ``(true or false) and false`` is false instead of true.
.. attention::
**calculations keep their datatype:**
**calculations keep their datatype even if the target variable is larger:**
When you do calculations on a BYTE type, the result will remain a BYTE.
When you do calculations on a WORD type, the result will remain a WORD.
For instance::
byte b = 44
word w = b*55 ; the result will be 116! (even though the target variable is a word)
w *= 999 ; the result will be -15188 (the multiplication stays within a word)
w *= 999 ; the result will be -15188 (the multiplication stays within a word, but overflows)
The compiler will NOT give a warning about this! It's doing this for
*The compiler does NOT warn about this!* It's doing this for
performance reasons - so you won't get sudden 16 bit (or even float)
calculations where you needed only simple fast byte arithmetic.
If you do need the extended resulting value, cast at least one of the
operands of an operator to the larger datatype. For example::
operands explicitly to the larger datatype. For example::
byte b = 44
word w = b*55.w ; the result will be 2420
w = (b as word)*55 ; same result
w = (b as word)*55
w = b*(55 as word)
@ -719,7 +727,7 @@ sort(array)
floating point values.
reverse(array)
Reverse the values in the array (in-place). Supports all data types including floats.
Reverse the values in the array (in-place).
Can be used after sort() to sort an array in descending order.
len(x)
@ -803,7 +811,7 @@ memset(address, numbytes, bytevalue)
Efficiently set a part of memory to the given (u)byte value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
Note that for clearing the character screen, very fast specialized subroutines are
available in the ``screen`` block (part of the ``c64textio`` or ``cx16textio`` modules)
available in the ``txt`` block (part of the ``textio`` module)
memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value.

View File

@ -33,6 +33,13 @@ This makes it easier to understand and relate the generated code. Examples::
Directives
-----------
.. data:: %target <target>
Level: module.
Global setting, specifies that this module can only work for the given compiler target.
If compiled with a different target, compilation is aborted with an error message.
.. data:: %output <type>
Level: module.
@ -60,7 +67,8 @@ Directives
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
including default IRQs during normal system operation.
When the program exits, a system reset is performed (because BASIC will be in a corrupt state).
It's not possible to return cleanly to BASIC when the program exits. The only choice is
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
are required to perform floating point operations (from the BASIC kernel). No clean exit is possible.
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
@ -71,9 +79,10 @@ Directives
except the few addresses mentioned above that are used by the system's IRQ routine.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code).
It's not possible to return cleanly to BASIC when the program exits. The only choice is
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
- style ``dontuse`` -- don't use *any* location in the zeropage.
Also read :ref:`zeropage`.
@ -110,33 +119,38 @@ Directives
Level: module, block.
Sets special compiler options.
For a module option, only the ``enable_floats`` option is recognised, which will tell the compiler
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
Otherwise, floating point support is not enabled.
When used in a block with the ``force_output`` option, it will force the block to be outputted
in the final program. Can be useful to make sure some
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
- For a module option, there is ``enable_floats``, which will tell the compiler
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
Otherwise, floating point support is not enabled.
- There's also ``no_sysinit`` which cause the resulting program to *not* include
the system re-initialization logic of clearing the screen, resetting I/O config etc. You'll have to
take care of that yourself. The program will just start running from whatever state the machine is in when the
program was launched.
- When used in a block with the ``force_output`` option, it will force the block to be outputted
in the final program. Can be useful to make sure some
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
Level: block.
This directive can only be used inside a block.
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
The optional offset and length can be used to select a particular piece of the file.
The file is located relative to the current working directory!
Level: block.
This directive can only be used inside a block.
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
The optional offset and length can be used to select a particular piece of the file.
The file is located relative to the current working directory!
.. data:: %asminclude "<filename>", "scopelabel"
Level: block.
This directive can only be used inside a block.
The assembler will include the file as raw assembly source text at this point,
prog8 will not process this at all, with one exception: the labels.
The scopelabel argument will be used as a prefix to access the labels from the included source code,
otherwise you would risk symbol redefinitions or duplications.
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
if the file can't be found there it is searched relative to the current directory.
Level: block.
This directive can only be used inside a block.
The assembler will include the file as raw assembly source text at this point,
prog8 will not process this at all, with one exception: the labels.
The scopelabel argument will be used as a prefix to access the labels from the included source code,
otherwise you would risk symbol redefinitions or duplications.
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
if the file can't be found there it is searched relative to the current directory.
.. data:: %breakpoint
@ -267,6 +281,7 @@ type identifier type storage size example var declara
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
``uword[]`` unsigned word array depends on value ``uword[] myvar = [1, 2, 3, 4]``
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]``
``str`` string (petscii) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte
=============== ======================= ================= =========================================
@ -289,7 +304,8 @@ of something with an operand starting with 1 or 0, you'll have to add a space in
- When an integer value ranges from 256..65535 the compiler sees it as a ``uword``. For -32768..32767 it's a ``word``.
- When a hex number has 3 or 4 digits, for example ``$0004``, it is seen as a ``word`` otherwise as a ``byte``.
- When a binary number has 9 to 16 digits, for example ``%1100110011``, it is seen as a ``word`` otherwise as a ``byte``.
- You can force a byte value into a word value by adding the ``.w`` datatype suffix to the number: ``$2a.w`` is equivalent to ``$002a``.
- If the number fits in a byte but you really require it as a word value, you'll have to explicitly cast it: ``60 as uword``
or you can use the full word hexadecimal notation ``$003c``.
Data type conversion
@ -383,7 +399,7 @@ After defining a struct you can use the name of the struct as a data type to dec
Struct variables can be assigned a struct literal value (also in their declaration as initial value)::
Color rgb = {255, 100, 0} ; curly braces instead of brackets
Color rgb = [255, 100, 0] ; note that the value is an array
Operators
@ -514,18 +530,20 @@ and returning stuff in several registers as well. The ``clobbers`` clause is use
what CPU registers are clobbered by the call instead of being unchanged or returning a meaningful result value.
Subroutines that are implemented purely in assembly code and which have an assembly calling convention (i.e.
the parameters are strictly passed via cpu registers), are defined like this::
User subroutines in the program source code that are implemented purely in assembly and which have an assembly calling convention (i.e.
the parameters are strictly passed via cpu registers), are defined with ``asmsub`` like this::
asmsub FREADS32() clobbers(A,X,Y) {
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
%asm {{
lda $62
eor #$ff
asl a
lda #0
ldx #$a0
jmp $bc4f
}}
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
}}
}
the statement body of such a subroutine should consist of just an inline assembly block.

View File

@ -9,10 +9,15 @@ Prog8 targets the following hardware:
- optional use of memory mapped I/O registers
- optional use of system ROM routines
The main target machine is the well-known Commodore-64, which is an example of this.
Another (preliminary) supported target machine is the `CommanderX16 <https://www.commanderx16.com/>`_ .
Currently there are two machines that are supported as compiler target (selectable via the ``-target`` compiler argument):
This chapter explains the relevant system details of such machines.
- 'c64': the well-known Commodore-64, premium support
- 'cx16': the `CommanderX16 <https://www.commanderx16.com/>`_ a project from the 8-Bit Guy. Support for this is still experimental.
This chapter explains the relevant system details of these machines.
.. note::
If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
Memory Model

View File

@ -2,30 +2,28 @@
TODO
====
- optimize assignment codegeneration
- get rid of all TODO's ;-)
- get rid of all other TODO's in the code ;-)
- make it possible for array literals to not only contain compile time constants?
- implement @stack for asmsub parameters
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
- aliases for imported symbols for example perhaps '%alias print = c64scr.print' ?
- 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
- use VIC banking to move up the graphics bitmap memory location. Don't move it under the ROM though as that would require IRQ disabling and memory bank swapping for every bitmap manipulation
- add some primitives/subroutines/examples for using custom char sets, copying the default charset.
- recursive subroutines? via %option recursive, allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
More optimizations
^^^^^^^^^^^^^^^^^^
Add more compiler optimizations to the existing ones.
- more targeted optimizations for assigment asm code, such as the following:
- further optimize assignment codegeneration, such as the following:
- binexpr splitting (beware self-referencing expressions and asm code ballooning though)
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
- remove unreachable code after an exit(), return or goto
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
the program will then rely solely on the values as they are in memory at the time of program startup.
- Also some library routines and code patterns could perhaps be optimized further
- can the parameter passing to subroutines be optimized to avoid copying?
- can such parameter passing to subroutines be optimized to avoid copying?
- more optimizations on the language AST level
- more optimizations on the final assembly source level
- note: abandoned subroutine inlining because of problems referencing non-local stuff. Can't move everything around.
- note: subroutine inlining is abandoned because of problems referencing non-local stuff. Can't move everything around.
Eval stack redesign? (lot of work)

View File

@ -1,5 +1,5 @@
%import c64flt
%import c64textio
%import floats
%import textio
%zeropage basicsafe
main {

View File

@ -1,4 +1,4 @@
%import c64textio
%import textio
%zeropage basicsafe

View File

@ -1,9 +1,7 @@
%import c64flt
%import c64textio
%import floats
%import textio
%zeropage basicsafe
; TODO implement signed byte/word DIV asm generation, fix unsigned DIV asm generation (for in-place)
main {
sub start() {
@ -11,17 +9,17 @@ main {
div_ubyte(100, 6, 16)
div_ubyte(255, 2, 127)
;div_byte(0, 1, 0) ; TODO implement
;div_byte(100, -6, -16) ; TODO implement
;div_byte(127, -2, -63) ; TODO implement
div_byte(0, 1, 0)
div_byte(100, -6, -16)
div_byte(127, -2, -63)
div_uword(0,1,0)
div_uword(40000,500,80)
div_uword(43211,2,21605)
;div_word(0,1,0) ; TODO implement
;div_word(-20000,500,-40) ; TODO implement
;div_word(-2222,2,-1111) ; TODO implement
div_word(0,1,0)
div_word(-20000,500,-40)
div_word(-2222,2,-1111)
div_float(0,1,0)
div_float(999.9,111.0,9.008108108108107)
@ -95,11 +93,11 @@ main {
txt.print("err! ")
txt.print("float ")
c64flt.print_f(a1)
floats.print_f(a1)
txt.print(" / ")
c64flt.print_f(a2)
floats.print_f(a2)
txt.print(" = ")
c64flt.print_f(r)
floats.print_f(r)
c64.CHROUT('\n')
}
}

View File

@ -1,5 +1,5 @@
%import c64flt
%import c64textio
%import floats
%import textio
%zeropage basicsafe
main {
@ -101,11 +101,11 @@ main {
txt.print("err! ")
txt.print("float ")
c64flt.print_f(a1)
floats.print_f(a1)
txt.print(" - ")
c64flt.print_f(a2)
floats.print_f(a2)
txt.print(" = ")
c64flt.print_f(r)
floats.print_f(r)
c64.CHROUT('\n')
}
}

View File

@ -1,5 +1,5 @@
%import c64flt
%import c64textio
%import floats
%import textio
%zeropage basicsafe
main {
@ -95,11 +95,11 @@ main {
txt.print("err! ")
txt.print("float ")
c64flt.print_f(a1)
floats.print_f(a1)
txt.print(" * ")
c64flt.print_f(a2)
floats.print_f(a2)
txt.print(" = ")
c64flt.print_f(r)
floats.print_f(r)
c64.CHROUT('\n')
}
}

View File

@ -1,5 +1,5 @@
%import c64flt
%import c64textio
%import floats
%import textio
%zeropage basicsafe
main {
@ -99,11 +99,11 @@ main {
txt.print("err! ")
txt.print("float ")
c64flt.print_f(a1)
floats.print_f(a1)
txt.print(" + ")
c64flt.print_f(a2)
floats.print_f(a2)
txt.print(" = ")
c64flt.print_f(r)
floats.print_f(r)
c64.CHROUT('\n')
}
}

View File

@ -1,5 +1,5 @@
%import c64flt
%import c64textio
%import floats
%import textio
%zeropage basicsafe
main {
@ -132,9 +132,9 @@ main {
else
txt.print("err! ")
txt.print(" float ")
c64flt.print_f(value)
floats.print_f(value)
c64.CHROUT(',')
c64flt.print_f(expected)
floats.print_f(expected)
c64.CHROUT('\n')
}
}

View File

@ -1,4 +1,4 @@
%import c64textio
%import textio
%zeropage basicsafe
main {

View File

@ -1,5 +1,5 @@
%import c64flt
%import c64textio
%import floats
%import textio
%zeropage basicsafe
main {

View File

@ -0,0 +1,315 @@
%import textio
%import syslib
%zeropage basicsafe
main {
sub start() {
repeat 25 {
txt.chrout('\n')
}
ubyte ub
byte bb
uword uwsum
word wsum
uwsum = 50000
ub=50
uwsum += ub
ub=250
uwsum += ub
if uwsum==50300
txt.print("1 ok\n")
else {
txt.print("1 fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
}
wsum = -30000
bb = 100
wsum += bb
bb = -50
wsum += bb
if wsum==-29950
txt.print("2 ok\n")
else {
txt.print("2 fail:")
txt.print_w(wsum)
txt.chrout('\n')
}
uwsum = 50000
ub=50
uwsum -= ub
ub=250
uwsum -= ub
if uwsum==49700
txt.print("3 ok\n")
else {
txt.print("3 fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
}
wsum = -30000
bb = 100
wsum -= bb
bb = -50
wsum -= bb
if wsum==-30050
txt.print("4 ok\n")
else
txt.print("4 fail\n")
uwsum = 50000
bb=50
uwsum += bb as uword
bb=-100
uwsum += bb as uword
if uwsum==49950
txt.print("5 ok\n")
else
txt.print("5 fail\n")
uwsum = 50000
bb=50
uwsum -= bb as uword
bb=100
uwsum -= bb as uword
if uwsum==49850
txt.print("6 ok\n")
else {
txt.print("6 fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
}
wsum = -30000
ub = 50
wsum += ub
ub = 250
wsum += ub
if wsum==-29700
txt.print("7 ok\n")
else {
txt.print("7 fail:")
txt.print_w(wsum)
txt.chrout('\n')
}
wsum = -30000
ub = 50
wsum -= ub
ub = 250
wsum -= ub
if wsum==-30300
txt.print("8 ok\n")
else {
txt.print("8 fail:")
txt.print_w(wsum)
txt.chrout('\n')
}
txt.chrout('\n')
uwsum = 50000
ub=0
uwsum += (50+ub)
uwsum += (250+ub)
if uwsum==50300
txt.print("1b ok\n")
else {
txt.print("1b fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
}
bb = 0
wsum = -30000
wsum += (100+bb)
wsum += (-50+bb)
if wsum==-29950
txt.print("2b ok\n")
else {
txt.print("2b fail:")
txt.print_w(wsum)
txt.chrout('\n')
}
uwsum = 50000
uwsum -= (50+ub)
uwsum -= (250+ub)
if uwsum==49700
txt.print("3b ok\n")
else {
txt.print("3b fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
}
wsum = -30000
wsum -= (100+bb)
wsum -= (-50+bb)
if wsum==-30050
txt.print("4b ok\n")
else
txt.print("4b fail\n")
uwsum = 50000
uwsum += (50+bb) as uword
uwsum += (-100+bb) as uword
if uwsum==49950
txt.print("5b ok\n")
else
txt.print("5b fail\n")
uwsum = 50000
uwsum -= (50+bb) as uword
uwsum -= (100+bb) as uword
if uwsum==49850
txt.print("6b ok\n")
else {
txt.print("6b fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
}
wsum = -30000
wsum += (50+ub)
wsum += (250+ub)
if wsum==-29700
txt.print("7b ok\n")
else {
txt.print("7b fail:")
txt.print_w(wsum)
txt.chrout('\n')
}
wsum = -30000
wsum -= (50+ub)
wsum -= (250+ub)
if wsum==-30300
txt.print("8b ok\n")
else {
txt.print("8b fail:")
txt.print_w(wsum)
txt.chrout('\n')
}
txt.chrout('\n')
uwsum = 50000
uwsum += 50
uwsum += 250
if uwsum==50300
txt.print("1c ok\n")
else {
txt.print("1c fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
}
wsum = -30000
wsum += 100
wsum += -50
if wsum==-29950
txt.print("2c ok\n")
else {
txt.print("2c fail:")
txt.print_w(wsum)
txt.chrout('\n')
}
uwsum = 50000
uwsum -= 50
uwsum -= 250
if uwsum==49700
txt.print("3c ok\n")
else {
txt.print("3c fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
}
wsum = -30000
wsum -= 100
wsum -= -50
if wsum==-30050
txt.print("4c ok\n")
else
txt.print("4c fail\n")
uwsum = 50000
uwsum += 50 as uword
uwsum += -100 as uword
if uwsum==49950
txt.print("5c ok\n")
else
txt.print("5c fail\n")
uwsum = 50000
uwsum -= 50 as uword
uwsum -= 100 as uword
if uwsum==49850
txt.print("6c ok\n")
else {
txt.print("6c fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
}
wsum = -30000
wsum += 50
wsum += 250
if wsum==-29700
txt.print("7c ok\n")
else {
txt.print("7c fail:")
txt.print_w(wsum)
txt.chrout('\n')
}
wsum = -30000
wsum -= 50
wsum -= 250
if wsum==-30300
txt.print("8c ok\n")
else {
txt.print("8c fail:")
txt.print_w(wsum)
txt.chrout('\n')
}
}
}

View File

@ -1,5 +1,6 @@
%import c64lib
%import c64textio
%target c64
%import syslib
%import textio
%zeropage basicsafe
main {
@ -43,8 +44,10 @@ main {
}
perform_scroll = false
txt.scroll_left_full(true)
if c64.RASTER & 1
txt.scroll_left(true)
; float the balloon
if rnd() & %10000
c64.SPXY[1] ++
else
c64.SPXY[1] --

View File

@ -1,5 +1,6 @@
%import c64lib
%import c64textio
%target c64
%import syslib
%import textio
%zeropage basicsafe
main {

View File

@ -1,4 +1,6 @@
%import c64textio
%target c64
%import textio
%import syslib
main {
@ -47,10 +49,10 @@ sub delay() {
sub print_notes(ubyte n1, ubyte n2) {
c64.CHROUT('\n')
txt.plot(n1/2, 24)
c64.COLOR=7
txt.color(7)
c64.CHROUT('Q')
txt.plot(n2/2, 24)
c64.COLOR=4
txt.color(4)
c64.CHROUT('Q')
}

View File

@ -1,256 +0,0 @@
%import c64textio
; 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
clear_screen()
}
sub clear_screen() {
memset(bitmap_address, 320*200/8, 0)
txt.clear_screen($10, 0) ; pixel color $1 (white) backround $0 (black)
}
sub line(uword x1, ubyte y1, uword x2, ubyte y2) {
; Bresenham algorithm.
; This code special cases various quadrant loops to allow simple ++ and -- operations.
if y1>y2 {
; make sure dy is always positive to avoid 8 instead of just 4 special cases
swap(x1, x2)
swap(y1, y2)
}
word d = 0
ubyte positive_ix = true
word dx = x2 - x1 as word
word dy = y2 as word - y1 as word
if dx < 0 {
dx = -dx
positive_ix = false
}
dx *= 2
dy *= 2
plotx = x1
if dx >= dy {
if positive_ix {
repeat {
plot(y1)
if plotx==x2
return
plotx++
d += dy
if d > dx {
y1++
d -= dx
}
}
} else {
repeat {
plot(y1)
if plotx==x2
return
plotx--
d += dy
if d > dx {
y1++
d -= dx
}
}
}
}
else {
if positive_ix {
repeat {
plot(y1)
if y1 == y2
return
y1++
d += dx
if d > dy {
plotx++
d -= dy
}
}
} else {
repeat {
plot(y1)
if y1 == y2
return
y1++
d += dx
if d > dy {
plotx--
d -= dy
}
}
}
}
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm
ubyte ploty
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
while xx>=yy {
plotx = xcenter + xx
ploty = ycenter + yy
plot(ploty)
plotx = xcenter - xx
plot(ploty)
plotx = xcenter + xx
ploty = ycenter - yy
plot(ploty)
plotx = xcenter - xx
plot(ploty)
plotx = xcenter + yy
ploty = ycenter + xx
plot(ploty)
plotx = xcenter - yy
plot(ploty)
plotx = xcenter + yy
ploty = ycenter - xx
plot(ploty)
plotx = xcenter - yy
plot(ploty)
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
}
}
}
sub disc(uword cx, ubyte cy, ubyte radius) {
; Midpoint algorithm, filled
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
while xx>=yy {
ubyte cy_plus_yy = cy + yy
ubyte cy_min_yy = cy - yy
ubyte cy_plus_xx = cy + xx
ubyte cy_min_xx = cy - xx
for plotx in cx to cx+xx {
plot(cy_plus_yy)
plot(cy_min_yy)
}
for plotx in cx-xx to cx-1 {
plot(cy_plus_yy)
plot(cy_min_yy)
}
for plotx in cx to cx+yy {
plot(cy_plus_xx)
plot(cy_min_xx)
}
for plotx in cx-yy to cx {
plot(cy_plus_xx)
plot(cy_min_xx)
}
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
}
}
}
; here is the non-asm code for the plot routine below:
; sub plot_nonasm(uword px, ubyte py) {
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
; uword addr = bitmap_address + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; @(addr) |= ormask[lsb(px) & 7]
; }
uword plotx ; 0..319 ; separate 'parameter' for plot()
asmsub plot(ubyte ploty @A) { ; plotx is 16 bits 0 to 319... doesn't fit in a register
%asm {{
tay
stx P8ZP_SCRATCH_REG_X
lda plotx+1
sta P8ZP_SCRATCH_W2+1
lsr a ; 0
sta P8ZP_SCRATCH_W2
lda 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 ; 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)
_y_lookup_hi
.byte $20, $20, $20, $20, $20, $20, $20, $20, $21, $21, $21, $21, $21, $21, $21, $21
.byte $22, $22, $22, $22, $22, $22, $22, $22, $23, $23, $23, $23, $23, $23, $23, $23
.byte $25, $25, $25, $25, $25, $25, $25, $25, $26, $26, $26, $26, $26, $26, $26, $26
.byte $27, $27, $27, $27, $27, $27, $27, $27, $28, $28, $28, $28, $28, $28, $28, $28
.byte $2a, $2a, $2a, $2a, $2a, $2a, $2a, $2a, $2b, $2b, $2b, $2b, $2b, $2b, $2b, $2b
.byte $2c, $2c, $2c, $2c, $2c, $2c, $2c, $2c, $2d, $2d, $2d, $2d, $2d, $2d, $2d, $2d
.byte $2f, $2f, $2f, $2f, $2f, $2f, $2f, $2f, $30, $30, $30, $30, $30, $30, $30, $30
.byte $31, $31, $31, $31, $31, $31, $31, $31, $32, $32, $32, $32, $32, $32, $32, $32
.byte $34, $34, $34, $34, $34, $34, $34, $34, $35, $35, $35, $35, $35, $35, $35, $35
.byte $36, $36, $36, $36, $36, $36, $36, $36, $37, $37, $37, $37, $37, $37, $37, $37
.byte $39, $39, $39, $39, $39, $39, $39, $39, $3a, $3a, $3a, $3a, $3a, $3a, $3a, $3a
.byte $3b, $3b, $3b, $3b, $3b, $3b, $3b, $3b, $3c, $3c, $3c, $3c, $3c, $3c, $3c, $3c
.byte $3e, $3e, $3e, $3e, $3e, $3e, $3e, $3e
_y_lookup_lo
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07
}}
}
}

View File

@ -1,6 +1,7 @@
%import c64textio
%import textio
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
main {

View File

@ -1,7 +1,9 @@
%import c64textio
%import c64flt
%import textio
%import floats
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
main {
sub start() {

View File

@ -1,7 +1,9 @@
%import c64textio
%import textio
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
main {
sub start() {

View File

@ -1,6 +1,7 @@
%import c64textio
%import textio
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
main {

View File

@ -1,6 +1,7 @@
%import c64textio
%import textio
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
main {

1008
examples/cmp/comparisons.p8 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,93 +0,0 @@
%import c64textio
%zeropage basicsafe
main {
sub start() {
byte v1
byte v2
ubyte cr
txt.print("signed byte ")
cr=v1==v2
cr=v1==v2
cr=v1==v2
cr=v1!=v2
cr=v1!=v2
cr=v1!=v2
cr=v1<v2
cr=v1<v2
cr=v1<v2
cr=v1<v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1<=v2
cr=v1<=v2
cr=v1<=v2
cr=v1<=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
; comparisons:
v1=-20
v2=125
txt.print("v1=-20, v2=125\n")
compare()
v1=80
v2=80
txt.print("v1 = v2 = 80\n")
compare()
v1=20
v2=-111
txt.print("v1=20, v2=-111\n")
compare()
return
sub compare() {
txt.print(" == != < > <= >=\n")
if v1==v2
txt.print(" Q ")
else
txt.print(" . ")
if v1!=v2
txt.print(" Q ")
else
txt.print(" . ")
if v1<v2
txt.print(" Q ")
else
txt.print(" . ")
if v1>v2
txt.print(" Q ")
else
txt.print(" . ")
if v1<=v2
txt.print(" Q ")
else
txt.print(" . ")
if v1>=v2
txt.print(" Q ")
else
txt.print(" . ")
c64.CHROUT('\n')
}
}
}

View File

@ -1,110 +0,0 @@
%import c64textio
%import c64flt
%zeropage basicsafe
main {
sub start() {
float v1
float v2
ubyte cr
txt.print("floating point ")
cr=v1==v2
cr=v1==v2
cr=v1==v2
cr=v1!=v2
cr=v1!=v2
cr=v1!=v2
cr=v1<v2
cr=v1<v2
cr=v1<v2
cr=v1<v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1<=v2
cr=v1<=v2
cr=v1<=v2
cr=v1<=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
; comparisons:
v1=20
v2=666.66
txt.print("v1=20, v2=666.66\n")
compare()
v1=-20
v2=666.66
txt.print("v1=-20, v2=666.66\n")
compare()
v1=666.66
v2=555.55
txt.print("v1=666.66, v2=555.55\n")
compare()
v1=3.1415
v2=-3.1415
txt.print("v1 = 3.1415, v2 = -3.1415\n")
compare()
v1=3.1415
v2=3.1415
txt.print("v1 = v2 = 3.1415\n")
compare()
v1=0
v2=0
txt.print("v1 = v2 = 0\n")
compare()
return
sub compare() {
txt.print(" == != < > <= >=\n")
if v1==v2
txt.print(" Q ")
else
txt.print(" . ")
if v1!=v2
txt.print(" Q ")
else
txt.print(" . ")
if v1<v2
txt.print(" Q ")
else
txt.print(" . ")
if v1>v2
txt.print(" Q ")
else
txt.print(" . ")
if v1<=v2
txt.print(" Q ")
else
txt.print(" . ")
if v1>=v2
txt.print(" Q ")
else
txt.print(" . ")
c64.CHROUT('\n')
}
}
}

View File

@ -1,94 +0,0 @@
%import c64textio
%zeropage basicsafe
main {
sub start() {
ubyte v1
ubyte v2
ubyte cr
txt.print("unsigned byte ")
cr=v1==v2
cr=v1==v2
cr=v1==v2
cr=v1!=v2
cr=v1!=v2
cr=v1!=v2
cr=v1<v2
cr=v1<v2
cr=v1<v2
cr=v1<v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1<=v2
cr=v1<=v2
cr=v1<=v2
cr=v1<=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
; comparisons:
v1=20
v2=199
txt.print("v1=20, v2=199\n")
compare()
v1=80
v2=80
txt.print("v1 = v2 = 80\n")
compare()
v1=220
v2=10
txt.print("v1=220, v2=10\n")
compare()
return
sub compare() {
txt.print(" == != < > <= >=\n")
if v1==v2
txt.print(" Q ")
else
txt.print(" . ")
if v1!=v2
txt.print(" Q ")
else
txt.print(" . ")
if v1<v2
txt.print(" Q ")
else
txt.print(" . ")
if v1>v2
txt.print(" Q ")
else
txt.print(" . ")
if v1<=v2
txt.print(" Q ")
else
txt.print(" . ")
if v1>=v2
txt.print(" Q ")
else
txt.print(" . ")
c64.CHROUT('\n')
}
}
}

View File

@ -1,123 +0,0 @@
%import c64textio
%zeropage basicsafe
main {
sub start() {
uword v1
uword v2
ubyte cr
txt.print("unsigned word ")
cr=v1==v2
cr=v1==v2
cr=v1==v2
cr=v1!=v2
cr=v1!=v2
cr=v1!=v2
cr=v1<v2
cr=v1<v2
cr=v1<v2
cr=v1<v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1<=v2
cr=v1<=v2
cr=v1<=v2
cr=v1<=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
; comparisons:
v1=20
v2=$00aa
txt.print("v1=20, v2=$00aa\n")
compare()
v1=20
v2=$ea00
txt.print("v1=20, v2=$ea00\n")
compare()
v1=$c400
v2=$22
txt.print("v1=$c400, v2=$22\n")
compare()
v1=$c400
v2=$2a00
txt.print("v1=$c400, v2=$2a00\n")
compare()
v1=$c433
v2=$2a00
txt.print("v1=$c433, v2=$2a00\n")
compare()
v1=$c433
v2=$2aff
txt.print("v1=$c433, v2=$2aff\n")
compare()
v1=$aabb
v2=$aabb
txt.print("v1 = v2 = aabb\n")
compare()
v1=$aa00
v2=$aa00
txt.print("v1 = v2 = aa00\n")
compare()
v1=$aa
v2=$aa
txt.print("v1 = v2 = aa\n")
compare()
return
sub compare() {
txt.print(" == != < > <= >=\n")
if v1==v2
txt.print(" Q ")
else
txt.print(" . ")
if v1!=v2
txt.print(" Q ")
else
txt.print(" . ")
if v1<v2
txt.print(" Q ")
else
txt.print(" . ")
if v1>v2
txt.print(" Q ")
else
txt.print(" . ")
if v1<=v2
txt.print(" Q ")
else
txt.print(" . ")
if v1>=v2
txt.print(" Q ")
else
txt.print(" . ")
c64.CHROUT('\n')
}
}
}

View File

@ -1,159 +0,0 @@
%import c64textio
%zeropage basicsafe
main {
sub start() {
word v1
word v2
ubyte cr
txt.print("signed word ")
cr=v1==v2
cr=v1==v2
cr=v1==v2
cr=v1!=v2
cr=v1!=v2
cr=v1!=v2
cr=v1<v2
cr=v1<v2
cr=v1<v2
cr=v1<v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1>v2
cr=v1<=v2
cr=v1<=v2
cr=v1<=v2
cr=v1<=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
cr=v1>=v2
; comparisons:
v1=20
v2=$00aa
txt.print("v1=20, v2=$00aa\n")
compare()
v1=20
v2=$7a00
txt.print("v1=20, v2=$7a00\n")
compare()
v1=$7400
v2=$22
txt.print("v1=$7400, v2=$22\n")
compare()
v1=$7400
v2=$2a00
txt.print("v1=$7400, v2=$2a00\n")
compare()
v1=$7433
v2=$2a00
txt.print("v1=$7433, v2=$2a00\n")
compare()
v1=$7433
v2=$2aff
txt.print("v1=$7433, v2=$2aff\n")
compare()
; with negative numbers:
v1=-512
v2=$00aa
txt.print("v1=-512, v2=$00aa\n")
compare()
v1=-512
v2=$7a00
txt.print("v1=-512, v2=$7a00\n")
compare()
v1=$7400
v2=-512
txt.print("v1=$7400, v2=-512\n")
compare()
v1=-20000
v2=-1000
txt.print("v1=-20000, v2=-1000\n")
compare()
v1=-1000
v2=-20000
txt.print("v1=-1000, v2=-20000\n")
compare()
v1=-1
v2=32767
txt.print("v1=-1, v2=32767\n")
compare()
v1=32767
v2=-1
txt.print("v1=32767, v2=-1\n")
compare()
v1=$7abb
v2=$7abb
txt.print("v1 = v2 = 7abb\n")
compare()
v1=$7a00
v2=$7a00
txt.print("v1 = v2 = 7a00\n")
compare()
v1=$aa
v2=$aa
txt.print("v1 = v2 = aa\n")
compare()
return
sub compare() {
txt.print(" == != < > <= >=\n")
if v1==v2
txt.print(" Q ")
else
txt.print(" . ")
if v1!=v2
txt.print(" Q ")
else
txt.print(" . ")
if v1<v2
txt.print(" Q ")
else
txt.print(" . ")
if v1>v2
txt.print(" Q ")
else
txt.print(" . ")
if v1<=v2
txt.print(" Q ")
else
txt.print(" . ")
if v1>=v2
txt.print(" Q ")
else
txt.print(" . ")
c64.CHROUT('\n')
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More