Compare commits

...

206 Commits
v4.2 ... v4.6

Author SHA1 Message Date
2ba6c9ccbe textelite 1.1 finalize load/save, add it to examplesd disk 2020-10-20 21:49:06 +02:00
3eaf111e7d added 'slowwarn' cli option 2020-10-20 21:38:37 +02:00
30da26b9a9 tackling problem of invalid reuse of auto indexer var 2020-10-20 21:23:43 +02:00
e35ad0cc8f code cleanups 2020-10-20 17:54:16 +02:00
1a36302cf1 rest of optimizations following simplification of array indexer 2020-10-19 23:57:00 +02:00
82a28bb555 extra attempt to simplify add and subtract with negative numbers 2020-10-19 23:01:32 +02:00
c1ce0be451 slightly optimize expression code for most common cases +/- 1 , */div 2 2020-10-19 22:50:38 +02:00
c0a5f8fef0 removed double mul code 2020-10-19 21:32:44 +02:00
702cf304d0 implemented missing swap() operations 2020-10-19 21:26:11 +02:00
4dee8b6048 remove superfluous value eval 2020-10-19 02:38:26 +02:00
ec665e0cc1 fixed incorrect removal of certain assignments that are NOT double 2020-10-19 02:16:23 +02:00
aec3b82476 fixed bitshifting by more than the number of bits in the value 2020-10-19 02:05:01 +02:00
e83796b5b9 fixed bit shifting by 0. optimized bitshifting code. 2020-10-18 17:12:52 +02:00
8eb69d6eda vardecl with initializer expression are now optimized again (unless floats) 2020-10-18 16:15:05 +02:00
74b5124a42 removed restriction on array indexer expression again from docs and code... :) 2020-10-18 14:05:26 +02:00
b9706a180b fix array indexer bug 2020-10-18 13:49:53 +02:00
8aeb8a9bb7 reintroduce expressions for array indexing 2020-10-18 13:33:11 +02:00
8f2e166a22 annotated some high prio todos 2020-10-17 22:57:54 +02:00
fdd91170dc allow simple binary expressions as array indexing too, but not more 2020-10-17 22:43:35 +02:00
c40ddb061b example adjustments 2020-10-17 21:00:59 +02:00
353d6cfc55 doc about array index restriction 2020-10-17 20:35:36 +02:00
f37564c49c fixed 2020-10-17 19:59:48 +02:00
157484d94b adapted p8 code to restricted array indexing 2020-10-17 19:57:55 +02:00
7626c9fff7 only allow array indexing via a number, or a variable (eliminate complex expression calcs for array indexing, force explicit use of an index variable) 2020-10-17 19:57:55 +02:00
1f55f9fc49 removed 2 problematic ZP locations for the C64 2020-10-17 19:57:10 +02:00
2554bc7ef8 ordered the functions in the docs 2020-10-17 02:14:19 +02:00
7cb4100419 string can be compared directly (uses strcmp() automatically in asm) 2020-10-17 02:01:00 +02:00
2d3b7eb878 started making string compares use strcmp() automatically 2020-10-17 01:11:01 +02:00
4d01a78731 introduced strcmp() builtin function 2020-10-16 19:00:06 +02:00
a03e36828a fixed lines in assembly source optimizer 2020-10-16 01:48:03 +02:00
260fb65b06 making strcmp 2020-10-16 00:11:46 +02:00
9fb8526136 added conv.bin and hex string to number 2020-10-15 23:47:10 +02:00
26fc5ff5e2 preparing conv.bin and hex string to number 2020-10-15 23:10:28 +02:00
5060f0bb19 fixed assigning a memory byte from an array 2020-10-15 22:15:00 +02:00
beaf6d449b added short overview of the library modules 2020-10-15 21:30:03 +02:00
4d68b508a2 proper error if variable name is the same as its subroutine or block (that would create naming problems in the assembly code) 2020-10-15 20:48:18 +02:00
cd825e386d fix invalid address-of error when taking address of struct variable 2020-10-15 20:14:17 +02:00
095c8b2309 corrected name and added cx16logo library module for fun 2020-10-15 00:58:41 +02:00
8b6eb74c58 refactor 2020-10-14 23:43:38 +02:00
aba437e5a2 diskio load and save use kernel routines for load and save, and don't bother with SEQ files 2020-10-14 22:33:49 +02:00
efe3ed499b starting with load/save in textelite 2020-10-14 02:51:00 +02:00
5595564a1f todo strcmp 2020-10-14 01:22:43 +02:00
439761cb67 fixed C64 ZP addresses to allow disk I/O, introduced diskio library module 2020-10-14 01:17:18 +02:00
bee6c65293 fixed several bugs in the repeat assembly for loop sizes like 0 and 256 2020-10-13 21:48:15 +02:00
10145b946b invalid repeat loop code is generated... 2020-10-13 16:27:40 +02:00
ebf4b50059 reused existing CallGraph to check for recursion, which is now fixed. It's a warning too now. 2020-10-12 23:04:00 +02:00
07cce3b3fc version 4.5 2020-10-11 21:59:38 +02:00
f2c19afd95 version 4.5 2020-10-11 21:47:41 +02:00
d159e70e1c textelite travel commands 2020-10-11 21:38:25 +02:00
ac693a2541 textelite buy and sell commands 2020-10-11 19:29:18 +02:00
1e988116ce fixed precedence of comparison and bitwise operators 2020-10-11 19:02:53 +02:00
ec9e722927 added conv.str2byte and conv.str2ubyte 2020-10-11 18:36:20 +02:00
4cd5e8c378 textelite 2020-10-11 18:19:09 +02:00
b759d5e06a fixed X register corruption on Cx16 verions of float.GIVUAYFAY and GIVAYFAY 2020-10-11 17:46:19 +02:00
1469033c1e todo 2020-10-11 16:53:00 +02:00
c15fd75df7 asmassignment can now use arbitrary source symbols; optimized byte-word sign extesion with this to not use stack anymore 2020-10-11 15:44:08 +02:00
73524e01a6 really fix byte-word sign extension for function args as expression 2020-10-11 03:07:45 +02:00
9e54e11113 fixed string + string/ string * number 2020-10-11 02:34:04 +02:00
01ac5f29db fix byte-word sign extension for function args as expression 2020-10-11 01:38:34 +02:00
67a2241e32 textelite market start 2020-10-11 00:38:38 +02:00
72b6dc3de7 avoid crash when optimizer has multiple replacements of the same node 2020-10-11 00:37:35 +02:00
6f5b645995 textelite market start 2020-10-10 23:24:15 +02:00
458ad1de57 fix strlen on uword (pointer) instead of str 2020-10-10 23:24:05 +02:00
216f48b7c1 txtelite 2020-10-10 22:45:03 +02:00
b2d1757e5a asmgen: byte to word sign extensions 2020-10-10 15:39:48 +02:00
6e53eb9d5c asmgen: only generate storage byte for register saves in subroutine when it's actually needed 2020-10-10 15:02:56 +02:00
e5ee5be9c5 textelite 2020-10-10 04:42:17 +02:00
bd237b2b95 it's now possible in more places to assign arrays and put array literals without the need to define explicit variable. 2020-10-10 04:30:28 +02:00
d31cf766eb added missing doc picture 2020-10-10 02:51:02 +02:00
56d530ff04 txtelite with input loop 2020-10-10 01:46:19 +02:00
0bbb2240f2 txtelite with input loop 2020-10-10 01:35:46 +02:00
1c8e4dba73 added \' escape character 2020-10-10 01:28:57 +02:00
4a9956c4a4 txtelite species and planet naming fix 2020-10-10 01:15:26 +02:00
59c0e6ae32 added some more missing assignment codegens (word * byte etc) 2020-10-09 23:48:33 +02:00
94c30fc21e textelite 2020-10-09 22:47:42 +02:00
8bb3b3be20 fix repeat loop for variables when var == 0 2020-10-09 22:30:21 +02:00
85e3c2c5a2 textelite 2020-10-09 22:25:12 +02:00
4be381c597 fixed compiler optimizer crash because of conflicting expression replacements 2020-10-09 21:51:54 +02:00
6ff5470cf1 txtelite 2020-10-09 21:01:06 +02:00
151dcfdef9 code style 2020-10-08 21:47:07 +02:00
c282b4cb9f code style 2020-10-07 23:24:30 +02:00
c426f4626c added some more missing aug assign operator code 2020-10-07 22:53:18 +02:00
0e3c92626e fixed handling of main module when importing another. fixed diskdir closedown. 2020-10-07 21:55:00 +02:00
5099525e24 added missing register pair assignments. fixed compiler crashes 2020-10-07 03:43:02 +02:00
e22b4cbb67 fixed invalid errormessage about memory mapped strings 2020-10-07 01:35:39 +02:00
2b48828179 examples issues 2020-10-07 01:21:41 +02:00
3e181362dd optimized code for processing return values from asmsubs without intermediate estack. 2020-10-07 00:51:57 +02:00
71fd98e39e allow asmsub routines with multiple return values to be called (special case for return values in status register) 2020-10-07 00:33:42 +02:00
71cd8b6d51 cx16 cross-compile teaser screenshot 2020-10-05 19:59:51 +02:00
ad75fcbf7e txtelite 2020-10-05 19:49:13 +02:00
f8b04a6357 added status return flags to some kernel i/o operations 2020-10-05 19:48:21 +02:00
d8fcbb78d3 txtelite goatsoup 2020-10-04 21:53:16 +02:00
8408bf3789 another compiler crash fixed when dealing with functioncall returning a str 2020-10-04 21:53:08 +02:00
3e1185658e txtelite goatsoup 2020-10-04 21:35:37 +02:00
d778cdcd61 another compiler crash fixed when dealing with functioncall returning a str 2020-10-04 21:11:42 +02:00
90b303fc03 fix error message for invalid number of arguments 2020-10-04 19:28:22 +02:00
eb86b1270d txtelite 2020-10-04 19:23:36 +02:00
a1f0cc878b correct error message for faulty string variable declarations 2020-10-04 19:13:19 +02:00
f2e2720b15 compiler crash fixed when dealing with functioncall returning a str 2020-10-04 18:47:47 +02:00
ec8cfe1591 make string-assignment actually work (using strcpy) 2020-10-04 18:18:58 +02:00
22eac159e5 txtelite 2020-10-04 17:47:57 +02:00
956b0c3fa7 added \xHH escape character to strings, allow strings of length zero. 2020-10-04 13:05:43 +02:00
a6427e0949 added \$HH escape character to strings 2020-10-03 15:11:09 +02:00
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
181 changed files with 9368 additions and 5932 deletions

View File

@ -50,7 +50,7 @@ What use Prog8 provide?
- "c64": Commodore-64 (6510 CPU = almost a 6502) premium support.
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) experimental support.
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
@ -75,16 +75,18 @@ Example code
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64textio
%import textio
%zeropage basicsafe
main {
ubyte[256] sieve
ubyte candidate_prime = 2
ubyte candidate_prime = 2 ; is increased in the loop
sub start() {
memset(sieve, 256, false) ; clear the sieve
; clear the sieve, to reset starting situation on subsequent runs
memset(sieve, 256, false)
; calculate primes
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0
repeat {
@ -95,22 +97,23 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
txt.print(", ")
amount++
}
c64.CHROUT('\n')
txt.chrout('\n')
txt.print("number of primes (expected 54): ")
txt.print_ub(amount)
c64.CHROUT('\n')
txt.chrout('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0 ; we wrapped; no more primes available
return 0 ; we wrapped; no more primes available in the sieve
}
; found next one, mark the multiples and return it.
sieve[candidate_prime] = true
uword multiple = candidate_prime
while multiple < len(sieve) {
sieve[lsb(multiple)] = true
multiple += candidate_prime
@ -120,11 +123,11 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
}
when compiled an ran on a C-64 you'll get:
![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)
@ -136,3 +139,8 @@ Another example (cube3d-sprites.p8) draws the vertices of a rotating 3d cube:
If you want to play a video game, a fully working Tetris clone is included in the examples:
![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

@ -8,7 +8,7 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />

View File

@ -208,17 +208,6 @@ pop_float_fac2 .proc
jmp CONUPK
.pend
pop_float_to_indexed_var .proc
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jmp pop_float
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
@ -355,19 +344,19 @@ mul_f .proc
.pend
neg_f .proc
; -- push -flt back on stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr NEGOP
jmp push_fac1_as_result
; -- toggle the sign bit on the stack
lda P8ESTACK_HI+3,x
eor #$80
sta P8ESTACK_HI+3,x
rts
.pend
abs_f .proc
; -- push abs(float) on stack (as float)
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr ABS
jmp push_fac1_as_result
; -- strip the sign bit on the stack
lda P8ESTACK_HI+3,x
and #$7f
sta P8ESTACK_HI+3,x
rts
.pend
equal_f .proc
@ -707,13 +696,12 @@ sign_f .proc
set_0_array_float .proc
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
inx
lda P8ESTACK_LO,x
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ESTACK_LO,x
adc P8ZP_SCRATCH_B1
tay
lda #0
sta (P8ZP_SCRATCH_W1),y
@ -730,13 +718,12 @@ set_0_array_float .proc
set_array_float .proc
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
inx
lda P8ESTACK_LO,x
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ESTACK_LO,x
adc P8ZP_SCRATCH_B1
adc P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
bcc +

View File

@ -7,7 +7,7 @@
%target c64
%option enable_floats
c64flt {
floats {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
@ -50,22 +50,22 @@ romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; there is also c64flt.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
@ -207,11 +207,11 @@ sub print_f (float value) {
jsr c64.CHROUT
iny
bne -
ldx floats_store_reg
+ rts
+ ldx floats_store_reg
rts
}}
}
%asminclude "library:c64floats.asm", ""
%asminclude "library:c64/floats.asm", ""
}

View File

@ -1,12 +1,14 @@
%target c64
%import c64textio
%import textio
; bitmap pixel graphics module for the C64
; only black/white monchrome for now
; only black/white monchrome 320x200 for now
; assumes bitmap screen memory is $2000-$3fff
graphics {
const uword bitmap_address = $2000
const uword BITMAP_ADDRESS = $2000
const uword WIDTH = 320
const ubyte HEIGHT = 200
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
@ -16,7 +18,7 @@ graphics {
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
memset(bitmap_address, 320*200/8, 0)
memset(BITMAP_ADDRESS, 320*200/8, 0)
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
}
@ -31,8 +33,8 @@ graphics {
}
word @zp d = 0
ubyte positive_ix = true
word @zp dx = x2 - x1 as word
word @zp dy = y2 as word - y1 as word
word @zp dx = x2-x1 as word
word @zp dy = y2-y1
if dx < 0 {
dx = -dx
positive_ix = false
@ -135,33 +137,33 @@ graphics {
}
}
sub disc(uword cx, ubyte cy, ubyte radius) {
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm, filled
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
while xx>=yy {
ubyte cy_plus_yy = cy + yy
ubyte cy_min_yy = cy - yy
ubyte cy_plus_xx = cy + xx
ubyte cy_min_xx = cy - xx
ubyte ycenter_plus_yy = ycenter + yy
ubyte ycenter_min_yy = ycenter - yy
ubyte ycenter_plus_xx = ycenter + xx
ubyte ycenter_min_xx = ycenter - xx
for internal_plotx in cx to cx+xx {
internal_plot(cy_plus_yy)
internal_plot(cy_min_yy)
for internal_plotx in xcenter to xcenter+xx {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
}
for internal_plotx in cx-xx to cx-1 {
internal_plot(cy_plus_yy)
internal_plot(cy_min_yy)
for internal_plotx in xcenter-xx to xcenter-1 {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
}
for internal_plotx in cx to cx+yy {
internal_plot(cy_plus_xx)
internal_plot(cy_min_xx)
for internal_plotx in xcenter to xcenter+yy {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_min_xx)
}
for internal_plotx in cx-yy to cx {
internal_plot(cy_plus_xx)
internal_plot(cy_min_xx)
for internal_plotx in xcenter-yy to xcenter {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_min_xx)
}
yy++
if decisionOver2<=0
@ -177,7 +179,7 @@ graphics {
; here is the non-asm code for the plot routine below:
; sub plot_nonasm(uword px, ubyte py) {
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
; uword addr = bitmap_address + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; uword addr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; @(addr) |= ormask[lsb(px) & 7]
; }
@ -226,7 +228,7 @@ _ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; the y lookup tables encodes this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
_plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7)

View File

@ -11,6 +11,7 @@ c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $90 ; kernel status variable for I/O
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
@ -203,23 +204,23 @@ romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial
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 $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (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 $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (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 $FFCF = CHRIN() clobbers(X, 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 $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, 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 c64scr.plot for a 'safe' wrapper that preserves X.
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- end of C64 ROM kernal routines ----
@ -250,8 +251,7 @@ asmsub init_system() {
sta c64.COLOR
lda #0
sta c64.BGCOL0
tax
tay
jsr disable_runstop_and_charsetswitch
clc
clv
cli
@ -259,6 +259,26 @@ asmsub init_system() {
}}
}
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
%asm {{
sei
lda #14
sta $01 ; bank the kernal in
jmp (c64.RESET_VEC)
}}
}
asmsub disable_runstop_and_charsetswitch() {
%asm {{
lda #$80
sta 657 ; disable charset switching
lda #239
sta 808 ; disable run/stop key
rts
}}
}
asmsub set_irqvec_excl() clobbers(A) {
%asm {{
sei

View File

@ -4,23 +4,22 @@
;
; indent format: TABS, size=8
%target c64
%import c64lib
%import syslib
%import conv
txt {
asmsub clear_screen() {
%asm {{
lda #' '
jmp clear_screenchars
}}
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
sub clear_screen() {
clear_screenchars(' ')
}
asmsub fill_screen (ubyte char @ A, ubyte charcolor @ Y) clobbers(A) {
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
@ -50,7 +49,7 @@ _loop sta c64.Screen,y
}}
}
asmsub clear_screencolors (ubyte scrcolor @ A) clobbers(Y) {
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
; ---- clear the character screen colors with the given color (leaves characters).
; (assumes color matrix is at the default address)
%asm {{
@ -69,7 +68,15 @@ sub color (ubyte txtcol) {
c64.COLOR = txtcol
}
asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
sub lowercase() {
c64.VMCSB |= 2
}
sub uppercase() {
c64.VMCSB &= ~2
}
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
@ -108,7 +115,7 @@ _scroll_screen ; scroll the screen memory
}}
}
asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
@ -142,7 +149,7 @@ _scroll_screen ; scroll the screen memory
}}
}
asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
@ -176,7 +183,7 @@ _scroll_screen ; scroll the screen memory
}}
}
asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
@ -210,6 +217,8 @@ _scroll_screen ; scroll the screen memory
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace
@ -433,21 +442,22 @@ asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
}}
}
asmsub setchr (ubyte col @Y, ubyte row @A) clobbers(A) {
; ---- set the character in SCRATCH_ZPB1 on the screen matrix at the given position
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
; ---- sets the character in the screen matrix at the given position
%asm {{
sty P8ZP_SCRATCH_REG
pha
tya
asl a
tay
lda _screenrows+1,y
sta _mod+2
lda _screenrows,y
txa
clc
adc P8ZP_SCRATCH_REG
adc _screenrows,y
sta _mod+1
bcc +
inc _mod+2
+ lda P8ZP_SCRATCH_B1
+ pla
_mod sta $ffff ; modified
rts
@ -455,17 +465,18 @@ _screenrows .word $0400 + range(0, 1000, 40)
}}
}
asmsub getchr (ubyte col @Y, ubyte row @A) clobbers(Y) -> ubyte @ A {
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
%asm {{
sty P8ZP_SCRATCH_B1
pha
tya
asl a
tay
lda setchr._screenrows+1,y
sta _mod+2
lda setchr._screenrows,y
pla
clc
adc P8ZP_SCRATCH_B1
adc setchr._screenrows,y
sta _mod+1
bcc _mod
inc _mod+2
@ -474,21 +485,22 @@ _mod lda $ffff ; modified
}}
}
asmsub setclr (ubyte col @Y, ubyte row @A) clobbers(A) {
; ---- set the color in SCRATCH_ZPB1 on the screen matrix at the given position
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A, Y) {
; ---- set the color in A on the screen matrix at the given position
%asm {{
sty P8ZP_SCRATCH_REG
pha
tya
asl a
tay
lda _colorrows+1,y
sta _mod+2
lda _colorrows,y
txa
clc
adc P8ZP_SCRATCH_REG
adc _colorrows,y
sta _mod+1
bcc +
inc _mod+2
+ lda P8ZP_SCRATCH_B1
+ pla
_mod sta $ffff ; modified
rts
@ -496,17 +508,18 @@ _colorrows .word $d800 + range(0, 1000, 40)
}}
}
asmsub getclr (ubyte col @Y, ubyte row @A) clobbers(Y) -> ubyte @ A {
asmsub getclr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the color in the screen color matrix at the given location
%asm {{
sty P8ZP_SCRATCH_B1
pha
tya
asl a
tay
lda setclr._colorrows+1,y
sta _mod+2
lda setclr._colorrows,y
pla
clc
adc P8ZP_SCRATCH_B1
adc setclr._colorrows,y
sta _mod+1
bcc _mod
inc _mod+2
@ -553,4 +566,22 @@ asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
; -- returns the text screen width (number of columns)
%asm {{
jsr c64.SCREEN
txa
rts
}}
}
asmsub height() clobbers(X, Y) -> ubyte @A {
; -- returns the text screen height (number of rows)
%asm {{
jsr c64.SCREEN
tya
rts
}}
}
}

View File

@ -249,7 +249,28 @@ output .text "0000", $00 ; 0-terminated output buffer (to make printing ea
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY {
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the unsigned byte value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; TODO implement optimized custom version of this instead of simply reusing str2uword
%asm {{
jmp str2uword
}}
}
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the signed byte value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; TODO implement optimized custom version of this instead of simply reusing str2word
%asm {{
jmp str2word
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
@ -303,7 +324,7 @@ _result_times_10 ; (W*4 + W)*2
}}
}
asmsub str2word(str string @ AY) -> word @ AY {
asmsub str2word(str string @ AY) -> word @ AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
@ -358,4 +379,77 @@ _negative .byte 0
}}
}
asmsub hex2uword(str string @ AY) -> uword @AY {
; -- hexadecimal string with or without '$' to uword.
; string may be in petscii or c64-screencode encoding.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
sty P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'$'
beq _skip
cmp #7
bcc _add_nine
cmp #'9'
beq _calc
bcs _add_nine
_calc asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #$0f
clc
adc P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
_skip inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
_add_nine ldy #9
sty P8ZP_SCRATCH_B1
bne _calc
}}
}
asmsub bin2uword(str string @ AY) -> uword @AY {
; -- binary string with or without '%' to uword.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'%'
beq +
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
}}
}
}

View File

@ -7,7 +7,7 @@
%target cx16
%option enable_floats
c64flt {
floats {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
@ -25,7 +25,7 @@ c64flt {
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; there is also c64flt.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
@ -78,10 +78,10 @@ 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
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_REG
ldy P8ZP_SCRATCH_W2
jsr GIVAYF ; load it as signed... correct afterwards
lda P8ZP_SCRATCH_B1
bpl +
@ -98,9 +98,9 @@ _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
sta P8ZP_SCRATCH_W2
tya
ldy P8ZP_SCRATCH_REG
ldy P8ZP_SCRATCH_W2
jmp GIVAYF ; this uses the inverse order, Y/A
}}
}
@ -143,11 +143,11 @@ sub print_f (float value) {
jsr c64.CHROUT
iny
bne -
plx
+ rts
+ plx
rts
}}
}
%asminclude "library:c64floats.asm", ""
%asminclude "library:c64/floats.asm", ""
}

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

@ -12,117 +12,117 @@ 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
; STROUT --> use txt.print
; CLEARSCR -> use txt.clear_screen
; HOMECRSR -> use txt.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 $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 $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 $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 $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 $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (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) -> ubyte @Pc ; (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(X, 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 $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, 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 $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.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
; 65c02 hardware vectors:
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; the sixteen virtual 16-bit registers
&uword r0 = $02
&uword r1 = $04
&uword r2 = $06
&uword r3 = $08
&uword r4 = $0a
&uword r5 = $0c
&uword r6 = $0e
&uword r7 = $10
&uword r8 = $12
&uword r9 = $14
&uword r10 = $16
&uword r11 = $18
&uword r12 = $1a
&uword r13 = $1c
&uword r14 = $1e
&uword r15 = $20
&uword r0 = $0002
&uword r1 = $0004
&uword r2 = $0006
&uword r3 = $0008
&uword r4 = $000a
&uword r5 = $000c
&uword r6 = $000e
&uword r7 = $0010
&uword r8 = $0012
&uword r9 = $0014
&uword r10 = $0016
&uword r11 = $0018
&uword r12 = $001a
&uword r13 = $001c
&uword r14 = $001e
&uword r15 = $0020
; VERA registers
const uword VERA_BASE = $9F20
&ubyte VERA_ADDR_L = VERA_BASE + $00
&ubyte VERA_ADDR_M = VERA_BASE + $01
&ubyte VERA_ADDR_H = VERA_BASE + $02
&ubyte VERA_DATA0 = VERA_BASE + $03
&ubyte VERA_DATA1 = VERA_BASE + $04
&ubyte VERA_CTRL = VERA_BASE + $05
&ubyte VERA_IEN = VERA_BASE + $06
&ubyte VERA_ISR = VERA_BASE + $07
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $08
&ubyte VERA_DC_VIDEO = VERA_BASE + $09
&ubyte VERA_DC_HSCALE = VERA_BASE + $0A
&ubyte VERA_DC_VSCALE = VERA_BASE + $0B
&ubyte VERA_DC_BORDER = VERA_BASE + $0C
&ubyte VERA_DC_HSTART = VERA_BASE + $09
&ubyte VERA_DC_HSTOP = VERA_BASE + $0A
&ubyte VERA_DC_VSTART = VERA_BASE + $0B
&ubyte VERA_DC_VSTOP = VERA_BASE + $0C
&ubyte VERA_L0_CONFIG = VERA_BASE + $0D
&ubyte VERA_L0_MAPBASE = VERA_BASE + $0E
&ubyte VERA_L0_TILEBASE = VERA_BASE + $0F
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $10
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $11
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $12
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $13
&ubyte VERA_L1_CONFIG = VERA_BASE + $14
&ubyte VERA_L1_MAPBASE = VERA_BASE + $15
&ubyte VERA_L1_TILEBASE = VERA_BASE + $16
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $17
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $18
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $19
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $1A
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $1B
&ubyte VERA_AUDIO_RATE = VERA_BASE + $1C
&ubyte VERA_AUDIO_DATA = VERA_BASE + $1D
&ubyte VERA_SPI_DATA = VERA_BASE + $1E
&ubyte VERA_SPI_CTRL = VERA_BASE + $1F
&ubyte VERA_ADDR_L = VERA_BASE + $0000
&ubyte VERA_ADDR_M = VERA_BASE + $0001
&ubyte VERA_ADDR_H = VERA_BASE + $0002
&ubyte VERA_DATA0 = VERA_BASE + $0003
&ubyte VERA_DATA1 = VERA_BASE + $0004
&ubyte VERA_CTRL = VERA_BASE + $0005
&ubyte VERA_IEN = VERA_BASE + $0006
&ubyte VERA_ISR = VERA_BASE + $0007
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
; VERA_PSG_BASE = $1F9C0
; VERA_PALETTE_BASE = $1FA00
; VERA_SPRITES_BASE = $1FC00
@ -169,20 +169,18 @@ cx16 {
; ---- Commander X-16 additions on top of C64 kernal routines ----
; spelling of the names is taken from the Commander X-16 rom sources
; TODO specify the correct clobbers for alle these functions, for simplicity all 3 regs are marked clobbered atm
; supported C128 additions
romsub $ff4a = close_all() clobbers(A,X,Y)
romsub $ff59 = lkupla() clobbers(A,X,Y)
romsub $ff5c = lkupsa() clobbers(A,X,Y)
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
romsub $ff5f = screen_set_mode(ubyte mode @A) clobbers(A, X, Y) -> ubyte @Pc
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
romsub $ff65 = pfkey() clobbers(A,X,Y)
romsub $ff6e = jsrfar() clobbers(A,X,Y)
romsub $ff74 = fetch() clobbers(A,X,Y)
romsub $ff77 = stash() clobbers(A,X,Y)
romsub $ff7a = cmpare() clobbers(A,X,Y)
romsub $ff7d = primm() clobbers(A,X,Y)
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
romsub $ff6e = jsrfar()
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7d = primm()
; X16 additions
romsub $ff44 = macptr() clobbers(A,X,Y)
@ -193,7 +191,9 @@ romsub $ff71 = mouse_scan() clobbers(A, X, Y)
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff4d = clock_set_date_time() clobbers(A, X, Y) ; args: r0, r1, r2, r3L
romsub $ff50 = clock_get_date_time() clobbers(A) ; outout args: r0, r1, r2, r3L
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) ; outout args: r0, r1, r2, r3L
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
; high level graphics & fonts
romsub $ff20 = GRAPH_init() clobbers(A,X,Y) ; uses vectors=r0
@ -259,6 +259,14 @@ asmsub init_system() {
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
lda #$90 ; black
jsr c64.CHROUT
lda #1 ; swap fg/bg
jsr c64.CHROUT
lda #$9e ; yellow
jsr c64.CHROUT
lda #147 ; clear screen
jsr c64.CHROUT
lda #0
tax
tay
@ -269,4 +277,15 @@ asmsub init_system() {
}}
}
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
%asm {{
sei
lda #14
sta $01
stz cx16.d1prb ; bank the kernal in
jmp (cx16.RESET_VEC)
}}
}
}

View File

@ -0,0 +1,699 @@
; 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) {
txtcol &= 15
c64.CHROUT(color_to_charcode[txtcol])
}
sub color2 (ubyte txtcol, ubyte bgcol) {
txtcol &= 15
bgcol &= 15
c64.CHROUT(color_to_charcode[bgcol])
c64.CHROUT(1) ; switch fg and bg colors
c64.CHROUT(color_to_charcode[txtcol])
}
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

@ -0,0 +1,29 @@
%import textio
cx16logo {
sub logo_at(ubyte column, ubyte row) {
uword strptr
for strptr in logo_lines {
txt.plot(column, row)
txt.print(strptr)
row++
}
}
sub logo() {
uword strptr
for strptr in logo_lines
txt.print(strptr)
txt.chrout('\n')
}
str[] logo_lines = [
"\uf10d\uf11a\uf139\uf11b \uf11a\uf13a\uf11b\n",
"\uf10b\uf11a▎\uf139\uf11b \uf11a\uf13a\uf130\uf11b\n",
"\uf10f\uf11a▌ \uf139\uf11b \uf11a\uf13a \uf11b▌\n",
"\uf102 \uf132\uf11a▖\uf11b \uf11a▗\uf11b\uf132\n",
"\uf10e ▂\uf11a▘\uf11b \uf11a▝\uf11b▂\n",
"\uf104 \uf11a \uf11b\uf13a\uf11b \uf139\uf11a \uf11b\n",
"\uf101\uf130\uf13a \uf139▎\uf100"
]
}

View File

@ -1,344 +0,0 @@
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
%target cx16
%import cx16lib
%import conv
txt {
sub clear_screen() {
c64.CHROUT(147) ; clear screen (spaces)
}
asmsub fill_screen (ubyte char @ A, ubyte txtcolor @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
%asm {{
sta P8ZP_SCRATCH_W1 ; fillchar
sty P8ZP_SCRATCH_W1+1 ; textcolor
phx
jsr c64.SCREEN ; get dimensions in X/Y
dex
dey
txa
asl a
adc #1
sta P8ZP_SCRATCH_B1
- ldx P8ZP_SCRATCH_B1
- stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
and #$f0
ora P8ZP_SCRATCH_W1+1
sta cx16.VERA_DATA0
dex
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda P8ZP_SCRATCH_W1
sta cx16.VERA_DATA0
dex
cpx #255
bne -
dey
bpl --
plx
rts
}}
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
pha
phx
jsr c64.SCREEN ; get dimensions in X/Y
dex
dey
txa
asl a
sta P8ZP_SCRATCH_B1
pla
- ldx P8ZP_SCRATCH_B1
- stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
sta cx16.VERA_DATA0
dex
dex
cpx #254
bne -
dey
bpl --
plx
rts
}}
}
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
sub color (ubyte txtcol) {
c64.CHROUT(color_to_charcode[txtcol & 15])
}
sub color2 (ubyte txtcol, ubyte bgcol) {
c64.CHROUT(color_to_charcode[bgcol & 15])
c64.CHROUT(1) ; switch fg and bg colors
c64.CHROUT(color_to_charcode[txtcol & 15])
}
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to c64.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr c64.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
phx
jsr conv.ubyte2decimal
pha
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
txa
jsr c64.CHROUT
plx
rts
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
phx
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr c64.CHROUT
_ones txa
jsr c64.CHROUT
plx
rts
}}
}
asmsub print_b (byte value @ A) clobbers(A,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
phx
pha
cmp #0
bpl +
lda #'-'
jsr c64.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
phx
bcc +
pha
lda #'$'
jsr c64.CHROUT
pla
+ jsr conv.ubyte2hex
jsr c64.CHROUT
tya
jsr c64.CHROUT
plx
rts
}}
}
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
phx
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr c64.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr c64.CHROUT
dey
bne -
plx
rts
}}
}
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
phx
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr c64.CHROUT
iny
bne -
+ plx
rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
phx
jsr conv.uword2decimal
plx
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr c64.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp c64.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr c64.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
; TODO implement the "missing" txtio subroutines
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
phx
lda column
asl a
tax
ldy row
lda charcolor
and #$0f
sta P8ZP_SCRATCH_B1
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
and #$f0
ora P8ZP_SCRATCH_B1
sta cx16.VERA_DATA0
plx
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
%asm {{
phx
tax
clc
jsr c64.PLOT
plx
rts
}}
}
}

View File

@ -0,0 +1,163 @@
%import textio
%import syslib
; Note: this code is compatible with C64 and CX16.
diskio {
sub directory(ubyte drivenumber) -> byte {
; -- Shows the directory contents of disk drive 8-11 (provide as argument).
c64.SETNAM(1, "$")
c64.SETLFS(1, drivenumber, 0)
void c64.OPEN() ; open 1,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(1) ; use #1 as input channel
if_cs
goto io_error
repeat 4 {
void c64.CHRIN() ; skip the 4 prologue bytes
}
; while not key pressed / EOF encountered, read data.
ubyte status = c64.READST()
while not status {
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN()))
txt.chrout(' ')
ubyte @zp char
do {
char = c64.CHRIN()
txt.chrout(char)
} until char==0
txt.chrout('\n')
void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN()
status = c64.READST()
void c64.STOP()
if_nz
break
}
io_error:
status = c64.READST()
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(1)
if status and status != 64 { ; 64=end of file
txt.print("\ni/o error, status: ")
txt.print_ub(status)
txt.chrout('\n')
return false
}
return true
}
sub status(ubyte drivenumber) {
; -- display the disk drive's current status message
c64.SETNAM(0, $0000)
c64.SETLFS(15, drivenumber, 15)
void c64.OPEN() ; open 15,8,15
if_cs
goto io_error
void c64.CHKIN(15) ; use #15 as input channel
if_cs
goto io_error
while not c64.READST()
txt.chrout(c64.CHRIN())
io_error:
c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(15)
}
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> byte {
c64.SETNAM(strlen(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0)
uword end_address = address + size
%asm {{
lda address
sta P8ZP_SCRATCH_W1
lda address+1
sta P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_REG
lda #<P8ZP_SCRATCH_W1
ldx end_address
ldy end_address+1
jsr c64.SAVE
php
ldx P8ZP_SCRATCH_REG
plp
}}
if_cc
return c64.READST()==0
return false
}
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
c64.SETNAM(strlen(filenameptr), filenameptr)
ubyte secondary = 1
uword end_of_load = 0
if address_override
secondary = 0
c64.SETLFS(1, drivenumber, secondary)
%asm {{
stx P8ZP_SCRATCH_REG
lda #0
ldx address_override
ldy address_override+1
jsr c64.LOAD
bcs +
stx end_of_load
sty end_of_load+1
+ ldx P8ZP_SCRATCH_REG
}}
if end_of_load
return end_of_load - address_override
return 0
}
str filename = "0:??????????????????????????????????????"
sub delete(ubyte drivenumber, uword filenameptr) {
; -- delete a file on the drive
ubyte flen = strlen(filenameptr)
filename[0] = 's'
filename[1] = ':'
memcopy(filenameptr, &filename+2, flen+1)
c64.SETNAM(flen+2, filename)
c64.SETLFS(1, drivenumber, 15)
void c64.OPEN()
c64.CLRCHN()
c64.CLOSE(1)
}
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
; -- rename a file on the drive
ubyte flen_old = strlen(oldfileptr)
ubyte flen_new = strlen(newfileptr)
filename[0] = 'r'
filename[1] = ':'
memcopy(newfileptr, &filename+2, flen_new)
ubyte fis_ix = flen_new+2 ; TODO is temp var for array indexing
filename[fis_ix] = '='
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
c64.SETNAM(3+flen_new+flen_old, filename)
c64.SETLFS(1, drivenumber, 15)
void c64.OPEN()
c64.CLRCHN()
c64.CLOSE(1)
}
}

View File

@ -31,7 +31,7 @@ _enterloop lsr P8ZP_SCRATCH_REG
.pend
multiply_bytes_16 .proc
multiply_bytes_into_word .proc
; -- multiply 2 bytes A and Y, result as word in A/Y (unsigned)
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
@ -781,7 +781,7 @@ mul_byte_3 .proc
sta P8ZP_SCRATCH_REG
asl a
clc
adc P8P_P8ZP_SCRATCH_REG
adc P8ZP_SCRATCH_REG
rts
.pend
@ -1469,3 +1469,22 @@ shift_right_w_3 .proc
jmp shift_right_w_7._shift3
.pend
; support for bit shifting that is too large to be unrolled:
lsr_byte_A .proc
; -- lsr signed byte in A times the value in Y (assume >0)
cmp #0
bmi _negative
- lsr a
dey
bne -
rts
_negative lsr a
ora #$80
dey
bne _negative
rts
.pend

View File

@ -40,16 +40,6 @@ add_a_to_zpword .proc
+ rts
.pend
pop_index_times_5 .proc
inx
lda P8ESTACK_LO,x
asl a
asl a
clc
adc P8ESTACK_LO,x
rts
.pend
neg_b .proc
lda #0
sec
@ -682,6 +672,7 @@ func_read_flags .proc
func_sqrt16 .proc
; TODO is this one faster? http://6502org.wikidot.com/software-math-sqrt
lda P8ESTACK_LO+1,x
sta P8ZP_SCRATCH_W2
lda P8ESTACK_HI+1,x
@ -1976,7 +1967,7 @@ ror2_array_uw .proc
strcpy .proc
; copy a string (0-terminated) from A/Y to (ZPWORD1)
; copy a string (must be 0-terminated) from A/Y to (P8ZP_SCRATCH_W1)
; it is assumed the target string is large enough.
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
@ -1989,6 +1980,38 @@ strcpy .proc
.pend
strcmp_mem .proc
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
lda (P8ZP_SCRATCH_W1),y
bne +
lda (P8ZP_SCRATCH_W2),y
bne _return_minusone
beq _return
+ lda (P8ZP_SCRATCH_W2),y
sec
sbc (P8ZP_SCRATCH_W1),y
bmi _return_one
bne _return_minusone
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_return_one
lda #1
_return rts
_return_minusone
lda #-1
rts
.pend
func_leftstr .proc
; leftstr(source, target, length) with params on stack
inx
@ -2097,3 +2120,18 @@ _startloop dey
rts
.pend
func_strcmp .proc
inx
lda P8ESTACK_LO,x
sta P8ZP_SCRATCH_W2
lda P8ESTACK_HI,x
sta P8ZP_SCRATCH_W2+1
lda P8ESTACK_HI+1,x
tay
lda P8ESTACK_LO+1,x
jsr strcmp_mem
sta P8ESTACK_LO+1,x
rts
.pend

View File

@ -5,5 +5,5 @@
; indent format: TABS, size=8
prog8_lib {
%asminclude "library:prog8lib.asm", ""
%asminclude "library:prog8_lib.asm", ""
}

View File

@ -1 +1 @@
4.2
4.6

View File

@ -38,6 +38,7 @@ private fun compileMain(args: Array<String>) {
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 slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation")
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)
@ -62,7 +63,7 @@ private fun compileMain(args: Array<String>) {
println("Continuous watch mode active. Main module: $filepath")
try {
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) {
print(" ")
@ -87,7 +88,7 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {

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,10 +115,14 @@ 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)
decl.arraysize!!.indexNum?.accept(this)
decl.arraysize!!.indexVar?.accept(this)
}
if(decl.isArray)
output("]")
@ -338,7 +344,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) {
@ -347,9 +353,10 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
arrayIndexedExpression.identifier.accept(this)
arrayIndexedExpression.arrayvar.accept(this)
output("[")
arrayIndexedExpression.arrayspec.index.accept(this)
arrayIndexedExpression.indexer.indexNum?.accept(this)
arrayIndexedExpression.indexer.indexVar?.accept(this)
output("]")
}

View File

@ -36,6 +36,18 @@ interface Node {
throw FatalAstException("scope missing from $this")
}
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
fun containingStatement(): Statement {
if(this is Statement)
return this
return findParentNode<Statement>(this)!!
}
fun replaceChildNode(node: Node, replacement: Node)
}
@ -44,11 +56,20 @@ interface IFunctionCall {
var args: MutableList<Expression>
}
class AsmGenInfo {
var usedAutoArrayIndexerForStatements = mutableMapOf<String, MutableSet<Statement>>()
var usedRegsaveA = false
var usedRegsaveX = false
var usedRegsaveY = false
}
interface INameScope {
val name: String
val position: Position
val statements: MutableList<Statement>
val parent: Node
val asmGenInfo: AsmGenInfo
fun linkParents(parent: Node)
@ -137,7 +158,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()
@ -155,7 +184,6 @@ interface INameScope {
}
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoVars() = statements.all { it !is VarDecl }
fun containsNoCodeNorVars() = !containsCodeOrVars()
fun remove(stmt: Statement) {
@ -195,6 +223,14 @@ interface INameScope {
else
null
}
fun indexOfChild(stmt: Statement): Int {
val idx = statements.indexOfFirst { it===stmt }
if(idx>=0)
return idx
else
throw FatalAstException("attempt to find a non-child")
}
}
interface IAssignable {
@ -228,11 +264,11 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
override val position: Position = Position.DUMMY
override var parent: Node
get() = throw FatalAstException("program has no parent")
set(value) = throw FatalAstException("can't set parent of program")
set(_) = throw FatalAstException("can't set parent of program")
override fun linkParents(parent: Node) {
modules.forEach {
it.linkParents(this)
it.linkParents(namespace)
}
}
@ -252,10 +288,14 @@ class Module(override val name: String,
override lateinit var parent: Node
lateinit var program: Program
override val asmGenInfo = AsmGenInfo()
val importedBy = mutableListOf<Module>()
val imports = mutableSetOf<Module>()
var loadAddress: Int = 0 // can be set with the %address directive
val loadAddress: Int by lazy {
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
address
}
override fun linkParents(parent: Node) {
this.parent = parent
@ -280,8 +320,9 @@ class Module(override val name: String,
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>()
override val statements = mutableListOf<Statement>() // not used
override var parent: Node = ParentSentinel
override val asmGenInfo = AsmGenInfo()
override fun linkParents(parent: Node) {
modules.forEach { it.linkParents(this) }
@ -312,6 +353,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
@ -326,6 +375,7 @@ object BuiltinFunctionScopePlaceholder : INameScope {
override val position = Position("<<placeholder>>", 0, 0, 0)
override var statements = mutableListOf<Statement>()
override var parent: Node = ParentSentinel
override val asmGenInfo = AsmGenInfo()
override fun linkParents(parent: Node) {}
}

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
}
@ -397,7 +397,7 @@ private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
}
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
fun makeLiteral(text: String, radix: Int): NumericLiteral {
val integer: Int
var datatype = DataType.UBYTE
when (radix) {
@ -435,14 +435,14 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
}
else -> throw FatalAstException("invalid radix")
}
return NumericLiteral(integer, if (forceWord) DataType.UWORD else datatype)
return NumericLiteral(integer, datatype)
}
val terminal: TerminalNode = children[0] as TerminalNode
val integerPart = this.intpart.text
return when (terminal.symbol.type) {
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null)
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null)
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null)
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10)
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
else -> throw FatalAstException(terminal.text)
}
}
@ -470,7 +470,6 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
litval.stringliteral()!=null -> litval.stringliteral().toAst()
litval.charliteral()!=null -> {
try {
val cc=litval.charliteral()
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
@ -647,7 +646,20 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
)
}
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
internal fun escape(str: String): String {
val es = str.map {
when(it) {
'\t' -> "\\t"
'\n' -> "\\n"
'\r' -> "\\r"
'"' -> "\\\""
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
in '\u0000'..'\u00ff' -> it.toString()
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
}
}
return es.joinToString("")
}
internal fun unescape(str: String, position: Position): String {
val result = mutableListOf<Char>()
@ -661,9 +673,15 @@ internal fun unescape(str: String, position: Position): String {
'n' -> '\n'
'r' -> '\r'
'"' -> '"'
'\'' -> '\''
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
'x' -> {
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
(0x8000 + hex).toChar()
}
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
})
} else {

View File

@ -39,16 +39,20 @@ enum class DataType {
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
infix fun largerThan(other: DataType) =
when(this) {
in ByteDatatypes -> false
in WordDatatypes -> other in ByteDatatypes
when {
this == other -> false
this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> false
else -> true
}
infix fun equalsSize(other: DataType) =
when(this) {
in ByteDatatypes -> other in ByteDatatypes
in WordDatatypes -> other in WordDatatypes
when {
this == other -> true
this in ByteDatatypes -> other in ByteDatatypes
this in WordDatatypes -> other in WordDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> true
else -> false
}
@ -165,6 +169,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

@ -3,6 +3,7 @@ package prog8.ast.base
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.ast.statements.Directive
import prog8.compiler.CompilationOptions
import prog8.compiler.BeforeAsmGenerationAstChanger
@ -18,8 +19,8 @@ internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
fixer.applyModifications()
}
internal fun Program.reorderStatements() {
val reorder = StatementReorderer(this)
internal fun Program.reorderStatements(errors: ErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
reorder.applyModifications()
}
@ -41,12 +42,6 @@ internal fun Module.checkImportedValid() {
imr.applyModifications()
}
internal fun Program.checkRecursion(errors: ErrorReporter) {
val checker = AstRecursionChecker(namespace, errors)
checker.visit(this)
checker.processMessages(name)
}
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val checker2 = AstIdentifiersChecker(this, errors)
@ -56,6 +51,9 @@ internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val transforms = AstVariousTransforms(this)
transforms.visit(this)
transforms.applyModifications()
val lit2decl = LiteralsToAutoVars(this)
lit2decl.visit(this)
lit2decl.applyModifications()
}
if (modules.map { it.name }.toSet().size != modules.size) {
@ -68,3 +66,37 @@ internal fun Program.variousCleanups() {
process.visit(this)
process.applyModifications()
}
internal fun Program.moveMainAndStartToFirst() {
// the module containing the program entrypoint is moved to the first in the sequence.
// the "main" block containing the entrypoint is moved to the top in there,
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
val directives = modules[0].statements.filterIsInstance<Directive>()
val start = this.entrypoint()
if(start!=null) {
val mod = start.definingModule()
val block = start.definingBlock()
if(!modules.remove(mod))
throw FatalAstException("module wrong")
modules.add(0, mod)
mod.remove(block)
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
mod.statements.add(block)
else
mod.statements.add(afterDirective, block)
block.remove(start)
afterDirective = block.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
block.statements.add(start)
else
block.statements.add(afterDirective, start)
// overwrite the directives in the module containing the entrypoint
for(directive in directives) {
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
modules[0].statements.add(0, directive)
}
}
}

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)
@ -41,8 +41,8 @@ sealed class Expression: Node {
&& other.left isSameAs left
&& other.right isSameAs right)
is ArrayIndexedExpression -> {
(other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index)
(other is ArrayIndexedExpression && other.arrayvar.nameInSource == arrayvar.nameInSource
&& other.indexer isSameAs indexer)
}
is DirectMemoryRead -> {
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
@ -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)
@ -232,20 +232,19 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
}
}
class ArrayIndexedExpression(var identifier: IdentifierReference,
val arrayspec: ArrayIndex,
class ArrayIndexedExpression(var arrayvar: IdentifierReference,
val indexer: ArrayIndex,
override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.linkParents(this)
arrayspec.linkParents(this)
arrayvar.linkParents(this)
indexer.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===identifier -> identifier = replacement as IdentifierReference
node===arrayspec.index -> arrayspec.index = replacement as Expression
node===arrayvar -> arrayvar = replacement as IdentifierReference
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
@ -255,10 +254,10 @@ 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) = arrayvar.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val target = identifier.targetStatement(program.namespace)
val target = arrayvar.targetStatement(program.namespace)
if (target is VarDecl) {
return when (target.datatype) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
@ -270,7 +269,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
}
override fun toString(): String {
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
}
}
@ -291,7 +290,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 +321,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 +344,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 +397,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 +497,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 +532,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 +568,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 +637,7 @@ class RangeExpr(var from: Expression,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
override fun referencesIdentifier(vararg scopedName: String): Boolean = from.referencesIdentifier(*scopedName) || to.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program)
val toDt=to.inferType(program)
@ -744,7 +750,8 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
override fun referencesIdentifier(vararg scopedName: String): Boolean =
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
return when (val targetStmt = targetStatement(program.namespace)) {
@ -766,6 +773,18 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
else -> throw FatalAstException("requires a reference value")
}
}
fun firstStructVarName(namespace: INameScope): String? {
// take the name of the first struct member of the structvariable instead
// if it's just a regular variable, return null.
val struct = memberOfStruct(namespace) ?: return null
val decl = targetVarDecl(namespace)!!
val firstStructMember = struct.nameOfFirstMember()
// find the flattened var that belongs to this first struct member
val firstVarName = listOf(decl.name, firstStructMember)
val firstVar = definingScope().lookup(firstVarName, this) as VarDecl
return firstVar.name
}
}
class FunctionCall(override var target: IdentifierReference,
@ -832,7 +851,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)
@ -852,6 +871,12 @@ class FunctionCall(override var target: IdentifierReference,
return InferredTypes.void() // no return value
if(stmt.returntypes.size==1)
return InferredTypes.knownFor(stmt.returntypes[0])
// multiple return values. Can occur for asmsub routines. If there is exactly one register return value, take that.
val numRegisterReturns = stmt.asmReturnvaluesRegisters.count { it.registerOrPair!=null }
if(numRegisterReturns==1)
return InferredTypes.InferredType.known(DataType.UBYTE)
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
}
else -> return InferredTypes.unknown()

View File

@ -120,11 +120,6 @@ 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)
@ -334,8 +329,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)
}
@ -346,17 +341,15 @@ internal class AstChecker(private val program: Program,
}
override fun visit(assignment: Assignment) {
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if (stmt is Subroutine && stmt.isAsmSubroutine) {
if(stmt.returntypes.size>1)
errors.err("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position)
else {
val idt = assignment.target.inferType(program, assignment)
if(!idt.isKnown || stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
errors.err("return type mismatch", assignment.value.position)
}
if (stmt is Subroutine) {
val idt = assignment.target.inferType(program, assignment)
if(!idt.isKnown) {
errors.err("return type mismatch", assignment.value.position)
}
if(stmt.returntypes.size <= 1 && stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
errors.err("return type mismatch", assignment.value.position)
}
}
}
@ -376,6 +369,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)
}
}
}
@ -383,7 +379,8 @@ internal class AstChecker(private val program: Program,
}
val targetDt = assignment.target.inferType(program, assignment)
if(assignment.value.inferType(program) != targetDt) {
val valueDt = assignment.value.inferType(program)
if(valueDt.isKnown && valueDt != targetDt) {
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
errors.err("cannot assign value to string or array", assignment.value.position)
else
@ -443,12 +440,8 @@ internal class AstChecker(private val program: Program,
checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal)
} else {
val sourceDatatype = assignment.value.inferType(program)
if (!sourceDatatype.isKnown) {
if (assignment.value is FunctionCall) {
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if (targetStmt != null)
errors.err("function call doesn't return a suitable value to use in assignment", assignment.value.position)
} else
if (sourceDatatype.isUnknown) {
if (assignment.value !is FunctionCall)
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position)
} else {
checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
@ -466,6 +459,7 @@ internal class AstChecker(private val program: Program,
else {
if(variable.datatype !in ArrayDatatypes
&& variable.type!=VarDeclType.MEMORY
&& variable.struct == null
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
errors.err("invalid pointer-of operand type", addressOf.position)
}
@ -476,7 +470,7 @@ internal class AstChecker(private val program: Program,
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true)
err("recursive var declaration")
// CONST can only occur on simple types (byte, word, float)
@ -584,13 +578,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)
}
}
}
@ -629,6 +623,14 @@ internal class AstChecker(private val program: Program,
}
}
// string assignment is not supported in a vard
if(decl.datatype==DataType.STR) {
if(decl.value==null)
err("string var must be initialized with a string literal")
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
err("string var can only be initialized with a string literal")
}
super.visit(decl)
}
@ -709,7 +711,7 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty())
err("missing option directive argument(s)")
else if(directive.args.map{it.name in setOf("enable_floats", "force_output")}.any { !it })
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit")}.any { !it })
err("invalid option directive argument(s)")
}
"%target" -> {
@ -742,6 +744,22 @@ 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.parent is VarDecl) {
if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
errors.err("array literal for variable initialization contains invalid types", array.position)
} else if(array.parent is ForLoop) {
if (!array.value.all { it.constValue(program) != null })
errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position)
}
super.visit(array)
}
@ -769,6 +787,8 @@ internal class AstChecker(private val program: Program,
}
override fun visit(expr: BinaryExpression) {
super.visit(expr)
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
@ -808,13 +828,12 @@ internal class AstChecker(private val program: Program,
}
}
if(leftDt !in NumericDatatypes)
errors.err("left operand is not numeric", expr.left.position)
if(rightDt!in NumericDatatypes)
errors.err("right operand is not numeric", expr.right.position)
if(leftDt !in NumericDatatypes && leftDt != DataType.STR)
errors.err("left operand is not numeric or str", expr.left.position)
if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
errors.err("right operand is not numeric or str", expr.right.position)
if(leftDt!=rightDt)
errors.err("left and right operands aren't the same type", expr.left.position)
super.visit(expr)
}
override fun visit(typecast: TypecastExpression) {
@ -874,7 +893,29 @@ internal class AstChecker(private val program: Program,
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program)
if(error!=null)
errors.err(error, functionCall.args.first().position)
errors.err(error, functionCall.position)
// check the functions that return multiple returnvalues.
val stmt = functionCall.target.targetStatement(program.namespace)
if (stmt is Subroutine) {
if (stmt.returntypes.size > 1) {
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
// asmsub routines can have multiple return values, for instance in 2 different registers.
// It's not (yet) possible to handle these multiple return values because assignments
// are only to a single unique target at the same time.
// EXCEPTION:
// if the asmsub returns multiple values and one of them is via a status register bit,
// it *is* possible to handle them by just actually assigning the register value and
// dealing with the status bit as just being that, the status bit after the call.
val (returnRegisters, returnStatusflags) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
if (returnRegisters.isEmpty() || returnRegisters.size == 1) {
if (returnStatusflags.any())
errors.warn("this asmsub also has one or more return 'values' in one of the status flags", functionCall.position)
} else {
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCall.position)
}
}
}
super.visit(functionCall)
}
@ -967,7 +1008,7 @@ internal class AstChecker(private val program: Program,
}
}
} else if(postIncrDecr.target.arrayindexed != null) {
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program.namespace)
if(target==null) {
errors.err("undefined symbol", postIncrDecr.position)
}
@ -982,32 +1023,38 @@ internal class AstChecker(private val program: Program,
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
val target = arrayIndexedExpression.identifier.targetStatement(program.namespace)
val target = arrayIndexedExpression.arrayvar.targetStatement(program.namespace)
if(target is VarDecl) {
if(target.datatype !in IterableDatatypes)
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
val arraysize = target.arraysize?.constIndex()
if(arraysize!=null) {
// check out of bounds
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
val index = arrayIndexedExpression.indexer.constIndex()
if(index!=null && (index<0 || index>=arraysize))
errors.err("array index out of bounds", arrayIndexedExpression.arrayspec.position)
errors.err("array index out of bounds", arrayIndexedExpression.indexer.position)
} else if(target.datatype == DataType.STR) {
if(target.value is StringLiteralValue) {
// check string lengths for non-memory mapped strings
val stringLen = (target.value as StringLiteralValue).value.length
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
val index = arrayIndexedExpression.indexer.constIndex()
if (index != null && (index < 0 || index >= stringLen))
errors.err("index out of bounds", arrayIndexedExpression.arrayspec.position)
errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
}
}
} else
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
// check index value 0..255
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program).typeOrElse(DataType.STRUCT)
if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE)
val dtxNum = arrayIndexedExpression.indexer.indexNum?.inferType(program)?.typeOrElse(DataType.STRUCT)
if(dtxNum!=null && dtxNum != DataType.UBYTE && dtxNum != DataType.BYTE)
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
val dtxVar = arrayIndexedExpression.indexer.indexVar?.inferType(program)?.typeOrElse(DataType.STRUCT)
if(dtxVar!=null && dtxVar != DataType.UBYTE && dtxVar != DataType.BYTE)
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
if(arrayIndexedExpression.indexer.origExpression!=null)
throw FatalAstException("array indexer should have been replaced with a temp var @ ${arrayIndexedExpression.indexer.position}")
super.visit(arrayIndexedExpression)
}
@ -1098,7 +1145,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")
@ -1112,10 +1159,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>256)
return err("byte array length must be 1-256")
val constX = arrayspec.index.constValue(program)
if(constX?.type !in IntegerDatatypes)
return err("array size specifier must be constant integer value")
val expectedSize = constX!!.number.toInt()
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return true
@ -1134,10 +1178,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>128)
return err("word array length must be 1-128")
val constX = arrayspec.index.constValue(program)
if(constX?.type !in IntegerDatatypes)
return err("array size specifier must be constant integer value")
val expectedSize = constX!!.number.toInt()
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return true
@ -1156,10 +1197,7 @@ internal class AstChecker(private val program: Program,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize < 1 || arraySpecSize>51)
return err("float array length must be 1-51")
val constX = arrayspec.index.constValue(program)
if(constX?.type !in IntegerDatatypes)
return err("array size specifier must be constant integer value")
val expectedSize = constX!!.number.toInt()
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
} else

View File

@ -34,6 +34,16 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
super.visit(block)
}
override fun visit(directive: Directive) {
if(directive.directive=="%target") {
val compatibleTarget = directive.args.single().name
if (compatibleTarget != CompilationTarget.instance.name)
errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position)
}
super.visit(directive)
}
override fun visit(decl: VarDecl) {
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
@ -70,6 +80,11 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
if(decl.definingBlock().name==decl.name)
nameError(decl.name, decl.position, decl.definingBlock())
if(decl.definingSubroutine()?.name==decl.name)
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
super.visit(decl)
}
@ -88,14 +103,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()
@ -141,8 +148,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
}
override fun visit(string: StringLiteralValue) {
if (string.value.length !in 1..255)
errors.err("string literal length must be between 1 and 255", string.position)
if (string.value.length > 255)
errors.err("string literal length max is 255", string.position)
super.visit(string)
}

View File

@ -1,118 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
internal class AstRecursionChecker(private val namespace: INameScope,
private val errors: ErrorReporter) : IAstVisitor {
private val callGraph = DirectedGraph<INameScope>()
fun processMessages(modulename: String) {
val cycle = callGraph.checkForCycle()
if(cycle.isEmpty())
return
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", Position.DUMMY)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val scope = functionCallStatement.definingScope()
val targetStatement = functionCallStatement.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall) {
val scope = functionCall.definingScope()
val targetStatement = functionCall.target.targetStatement(namespace)
if(targetStatement!=null) {
val targetScope = when (targetStatement) {
is Subroutine -> targetStatement
else -> targetStatement.definingScope()
}
callGraph.add(scope, targetScope)
}
super.visit(functionCall)
}
private class DirectedGraph<VT> {
private val graph = mutableMapOf<VT, MutableSet<VT>>()
private var uniqueVertices = mutableSetOf<VT>()
val numVertices : Int
get() = uniqueVertices.size
fun add(from: VT, to: VT) {
var targets = graph[from]
if(targets==null) {
targets = mutableSetOf()
graph[from] = targets
}
targets.add(to)
uniqueVertices.add(from)
uniqueVertices.add(to)
}
fun print() {
println("#vertices: $numVertices")
graph.forEach { (from, to) ->
println("$from CALLS:")
to.forEach { println(" $it") }
}
val cycle = checkForCycle()
if(cycle.isNotEmpty()) {
println("CYCLIC! $cycle")
}
}
fun checkForCycle(): MutableList<VT> {
val visited = uniqueVertices.associateWith { false }.toMutableMap()
val recStack = uniqueVertices.associateWith { false }.toMutableMap()
val cycle = mutableListOf<VT>()
for(node in uniqueVertices) {
if(isCyclicUntil(node, visited, recStack, cycle))
return cycle
}
return mutableListOf()
}
private fun isCyclicUntil(node: VT,
visited: MutableMap<VT, Boolean>,
recStack: MutableMap<VT, Boolean>,
cycleNodes: MutableList<VT>): Boolean {
if(recStack[node]==true) return true
if(visited[node]==true) return false
// mark current node as visited and add to recursion stack
visited[node] = true
recStack[node] = true
// recurse for all neighbours
val neighbors = graph[node]
if(neighbors!=null) {
for (neighbour in neighbors) {
if (isCyclicUntil(neighbour, visited, recStack, cycleNodes)) {
cycleNodes.add(node)
return true
}
}
}
// pop node from recursion stack
recStack[node] = false
return false
}
}
}

View File

@ -10,44 +10,6 @@ import prog8.ast.statements.*
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource == listOf("swap")) {
// TODO don't replace swap(), let the code generator figure this all out
// if x and y are both just identifiers, do not rewrite (there should be asm generation for that)
// otherwise:
// rewrite swap(x,y) as follows:
// - declare a temp variable of the same datatype
// - temp = x, x = y, y= temp
val first = functionCallStatement.args[0]
val second = functionCallStatement.args[1]
if(first !is IdentifierReference && second !is IdentifierReference) {
val dt = first.inferType(program).typeOrElse(DataType.STRUCT)
val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}"
val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position)
val tempvar = IdentifierReference(listOf(tempname), first.position)
val assignTemp = Assignment(
AssignTarget(tempvar, null, null, first.position),
first,
first.position
)
val assignFirst = Assignment(
AssignTarget.fromExpr(first),
second,
first.position
)
val assignSecond = Assignment(
AssignTarget.fromExpr(second),
tempvar,
first.position
)
val scope = AnonymousScope(mutableListOf(tempvardecl, assignTemp, assignFirst, assignSecond), first.position)
return listOf(IAstModification.ReplaceNode(functionCallStatement, scope, parent))
}
}
return noModifications
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
@ -85,78 +47,59 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
return noModifications
}
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
when {
expr.left is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
parent
))
expr.right is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
parent
))
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftStr = expr.left as? StringLiteralValue
val rightStr = expr.right as? StringLiteralValue
if(expr.operator == "+") {
val concatenatedString = concatString(expr)
if(concatenatedString!=null)
return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent))
}
return noModifications
}
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl) {
// replace the literal string by a identifier reference to a new local vardecl
val vardecl = VarDecl.createAuto(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
)
}
return noModifications
}
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
val vardecl = array.parent as? VarDecl
if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess)
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
val cast = array.cast(vardecl.datatype)
if (cast != null && cast!=array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
else if(expr.operator == "*") {
if (leftStr!=null) {
val amount = expr.right.constValue(program)
if(amount!=null) {
val string = leftStr.value.repeat(amount.number.toInt())
val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
}
}
} else {
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
if(litval2!=null && litval2!=array) {
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope() as Node)
)
else if (rightStr!=null) {
val amount = expr.right.constValue(program)
if(amount!=null) {
val string = rightStr.value.repeat(amount.number.toInt())
val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
}
}
}
return noModifications
}
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
val constvalue = operand.constValue(program)
if(constvalue!=null) {
if (expr.operator == "*") {
// repeat a string a number of times
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
val rightStrval = expr.right as? StringLiteralValue
val leftStrval = expr.left as? StringLiteralValue
return when {
expr.operator!="+" -> null
expr.left is BinaryExpression && rightStrval!=null -> {
val subStrVal = concatString(expr.left as BinaryExpression)
if(subStrVal==null)
null
else
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position)
}
expr.right is BinaryExpression && leftStrval!=null -> {
val subStrVal = concatString(expr.right as BinaryExpression)
if(subStrVal==null)
null
else
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position)
}
leftStrval!=null && rightStrval!=null -> {
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position)
}
else -> null
}
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
}
return expr
}
}

View File

@ -9,79 +9,60 @@ import prog8.ast.statements.*
interface IAstModification {
fun perform()
class Remove(val node: Node, val parent: Node) : IAstModification {
class Remove(val node: Node, val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
} else {
throw FatalAstException("parent of a remove modification is not an INameScope")
}
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
}
}
class SetExpression(val setter: (newExpr: Expression) -> Unit, val newExpr: Expression, val parent: Node) : IAstModification {
class SetExpression(private val setter: (newExpr: Expression) -> Unit, private val newExpr: Expression, private val parent: Node) : IAstModification {
override fun perform() {
setter(newExpr)
newExpr.linkParents(parent)
}
}
class InsertFirst(val stmt: Statement, val parent: Node) : IAstModification {
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(0, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
parent.statements.add(0, stmt)
stmt.linkParents(parent as Node)
}
}
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
parent.statements.add(stmt)
stmt.linkParents(parent as Node)
}
}
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
val idx = parent.statements.indexOfFirst { it===after } + 1
parent.statements.add(idx, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
val idx = parent.statements.indexOfFirst { it===after } + 1
parent.statements.add(idx, stmt)
stmt.linkParents(parent as Node)
}
}
class InsertBefore(val before: Statement, val stmt: Statement, val parent: Node) : IAstModification {
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
if(parent is INameScope) {
val idx = parent.statements.indexOfFirst { it===before }
parent.statements.add(idx, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
val idx = parent.statements.indexOfFirst { it===before }
parent.statements.add(idx, stmt)
stmt.linkParents(parent as Node)
}
}
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) : IAstModification {
override fun perform() {
parent.replaceChildNode(node, replacement)
replacement.linkParents(parent)
}
}
class SwapOperands(val expr: BinaryExpression): IAstModification {
class SwapOperands(private 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 +209,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 +330,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)
}
@ -361,8 +343,8 @@ abstract class AstWalker {
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
arrayIndexedExpression.identifier.accept(this, arrayIndexedExpression)
arrayIndexedExpression.arrayspec.accept(this, arrayIndexedExpression)
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, 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)
}
@ -124,8 +125,8 @@ interface IAstVisitor {
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
arrayIndexedExpression.identifier.accept(this)
arrayIndexedExpression.arrayspec.accept(this)
arrayIndexedExpression.arrayvar.accept(this)
arrayIndexedExpression.indexer.accept(this)
}
fun visit(assignTarget: AssignTarget) {

View File

@ -1,5 +1,6 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.statements.Directive
@ -14,7 +15,7 @@ internal class ImportedModuleDirectiveRemover: AstWalker() {
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent))
return listOf(IAstModification.Remove(directive, parent as INameScope))
}
return noModifications
}

View File

@ -0,0 +1,53 @@
package prog8.ast.processing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
// replace the literal string by a identifier reference to a new local vardecl
val vardecl = VarDecl.createAuto(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope())
)
}
return noModifications
}
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
val vardecl = array.parent as? VarDecl
if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess)
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
val cast = array.cast(vardecl.datatype)
if (cast != null && cast !== array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
}
} else {
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
if(litval2!=null) {
val vardecl2 = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl2, array.definingScope())
)
}
}
}
return noModifications
}
}

View File

@ -6,12 +6,12 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
internal class StatementReorderer(val program: Program) : AstWalker() {
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last.
// - blocks are ordered by address, where blocks without address are placed last.
// - in every scope, most directives and vardecls are moved to the top.
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
// - the 'start' subroutine is moved to the top.
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
@ -71,6 +71,69 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
is NumericLiteralValue -> {
arrayIndexedExpression.indexer.indexNum = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is IdentifierReference -> {
arrayIndexedExpression.indexer.indexVar = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is Expression -> {
// replace complex indexing with a temp variable
return getAutoIndexerVarFor(arrayIndexedExpression)
}
else -> return noModifications
}
}
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val subroutine = expr.definingSubroutine()!!
val statement = expr.containingStatement()
val indexerVarPrefix = "prog8_autovar_index_"
val indexerVarName: String
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
val freeVar = repo.filter { statement !in it.value }.map { it.key }.firstOrNull()
if(freeVar==null) {
// add another loop index var to be used for this expression
val statementId = expr.hashCode()
indexerVarName = "$indexerVarPrefix$statementId"
// create the indexer var at block level scope
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
var statements = repo[indexerVarName]
if(statements==null) {
statements = mutableSetOf()
repo[indexerVarName] = statements
}
statements.add(statement)
} else {
// reuse an already created indexer autovar
repo.getValue(freeVar).add(statement)
indexerVarName = freeVar
}
// assign the indexing expression to the helper variable, and replace the indexer with just the variable
val indexerExpression = expr.indexer.origExpression!!
val target = AssignTarget(IdentifierReference(listOf(indexerVarName), indexerExpression.position), null, null, indexerExpression.position)
val assign = Assignment(target, indexerExpression, indexerExpression.position)
val beforeWhat = expr.containingStatement()
modifications.add(IAstModification.InsertBefore(beforeWhat, assign, beforeWhat.definingScope()))
modifications.add(IAstModification.SetExpression( {
expr.indexer.indexVar = it as IdentifierReference
expr.indexer.indexNum = null
expr.indexer.origExpression = null
}, target.identifier!!.copy(), expr.indexer))
return modifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
@ -81,23 +144,57 @@ 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
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
if(decl.datatype!=DataType.FLOAT) {
decl.value = null
decl.allowInitializeWithZero = false
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())
)
}
}
}
return noModifications
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment)
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
val assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
var assignments = emptyList<Assignment>()
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
} else {
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
}
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
modifications.add(IAstModification.Remove(assignment, parent))
return modifications
}
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
assignments = if (assignment.value is ArrayLiteralValue) {
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
} else {
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
}
}
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
val scope = assignment.definingScope()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
modifications.add(IAstModification.Remove(assignment, scope))
return modifications
}
return noModifications
}
@ -150,15 +247,55 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return noModifications
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val alv = assign.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
}
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!!
if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position)
return emptyList()
}
val alv = sourceVar.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
}
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
if(targetVar.arraysize==null) {
errors.err("array has no defined size", identifier.position)
return emptyList()
}
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
errors.err("element count mismatch", position)
return emptyList()
}
// TODO use a pointer loop instead of individual assignments
return alv.value.withIndex().map { (index, value)->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
Assignment(AssignTarget(null, idx, null, position), value, value.position)
}
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
val slv = structAssignment.value as? ArrayLiteralValue
if(slv==null || slv.value.size != struct.numberOfElements)
throw FatalAstException("element count mismatch")
if(slv==null || slv.value.size != struct.numberOfElements) {
errors.err("element count mismatch", structAssignment.position)
return emptyList()
}
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
targetDecl as VarDecl
@ -171,7 +308,8 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
}
}
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
// TODO use memcopy beyond a certain number of elements
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
@ -179,26 +317,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

@ -23,6 +23,11 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
val valueDt = declValue.inferType(program)
if(!valueDt.istype(decl.datatype)) {
// don't add a typecast on an array initializer value
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
return noModifications
return listOf(IAstModification.ReplaceNode(
declValue,
TypecastExpression(declValue, decl.datatype, true, declValue.position),
@ -124,10 +129,12 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node)
if(arg.second.value is IdentifierReference) {
modifications += IAstModification.ReplaceNode(
call.args[arg.second.index],
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node)
}
} else if(arg.second.value is NumericLiteralValue) {
val cast = (arg.second.value as NumericLiteralValue).cast(requiredType)
if(cast.isValid)

View File

@ -12,7 +12,7 @@ internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent))
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {

View File

@ -4,10 +4,9 @@ import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.statements.*
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
@ -26,20 +25,42 @@ 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)
if (target is Subroutine) {
// asmsub types are not checked specifically at this time
if(call.args.size != target.parameters.size)
return "invalid number of arguments"
val paramtypes = target.parameters.map { it.type }
val mismatch = argtypes.zip(paramtypes).indexOfFirst { it.first != it.second}
val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
if(mismatch>=0) {
val actual = argtypes[mismatch].toString()
val expected = paramtypes[mismatch].toString()
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
}
if(target.isAsmSubroutine) {
if(target.asmReturnvaluesRegisters.size>1) {
// multiple return values will NOT work inside an expression.
// they MIGHT work in a regular assignment or just a function call statement.
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
if(call !is FunctionCallStatement && parent !is Assignment && parent !is VarDecl) {
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)"
}
}
}
}
else if (target is BuiltinFunctionStatementPlaceholder) {
val func = BuiltinFunctions.getValue(target.name)
@ -47,7 +68,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,8 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.compiler.CompilerException
import prog8.compiler.target.CompilationTarget
sealed class Statement : Node {
@ -28,12 +30,6 @@ sealed class Statement : Node {
scope.add(name)
return scope.joinToString(".")
}
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
}
@ -56,6 +52,7 @@ class Block(override val name: String,
val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node
override val asmGenInfo = AsmGenInfo()
override fun linkParents(parent: Node) {
this.parent = parent
@ -175,6 +172,7 @@ open class VarDecl(val type: VarDeclType,
private set
var structHasBeenFlattened = false // set later
private set
var allowInitializeWithZero = true
// prefix for literal values that are turned into a variable on the heap
@ -214,8 +212,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
}
}
@ -232,7 +231,8 @@ open class VarDecl(val type: VarDeclType,
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===value)
// TODO the check that node===value is too strict sometimes, but leaving it out allows for bugs to creep through ... :( Perhaps check when adding the replace if there is already a replace on the same node?
require(replacement is Expression)
value = replacement
replacement.parent = this
}
@ -244,7 +244,12 @@ open class VarDecl(val type: VarDeclType,
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
}
fun zeroElementValue() = defaultZero(declaredDatatype, position)
fun zeroElementValue(): NumericLiteralValue {
if(allowInitializeWithZero)
return defaultZero(declaredDatatype, position)
else
throw CompilerException("attempt to get zero value for vardecl that shouldn't get it")
}
fun flattenStructMembers(): MutableList<Statement> {
val result = struct!!.statements.withIndex().map {
@ -273,34 +278,72 @@ class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Posit
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position)
class ArrayIndex(var index: Expression, override val position: Position) : Node {
class ArrayIndex(var origExpression: Expression?, // will be replaced later by either the number or the identifier
override val position: Position) : Node {
// for code simplicity, either indexed via a constant number or via a variable (no arbitrary expressions)
override lateinit var parent: Node
var indexNum: NumericLiteralValue? = origExpression as? NumericLiteralValue
var indexVar: IdentifierReference? = origExpression as? IdentifierReference
init {
if(indexNum!=null || indexVar!=null)
origExpression = null
}
override fun linkParents(parent: Node) {
this.parent = parent
index.linkParents(this)
origExpression?.linkParents(this)
indexNum?.linkParents(this)
indexVar?.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===index)
index = replacement
replacement.parent = this
require(replacement is Expression)
when {
node===origExpression -> origExpression = replacement
node===indexVar -> {
when (replacement) {
is NumericLiteralValue -> {
indexVar = null
indexNum = replacement
}
is IdentifierReference -> {
indexVar = replacement
}
else -> {
throw FatalAstException("invalid replace")
}
}
}
else -> throw FatalAstException("invalid replace")
}
}
companion object {
fun forArray(v: ArrayLiteralValue): ArrayIndex {
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
val indexnum = NumericLiteralValue.optimalNumeric(v.value.size, v.position)
return ArrayIndex(indexnum, v.position)
}
}
fun accept(visitor: IAstVisitor) = index.accept(visitor)
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
override fun toString(): String {
return("ArrayIndex($index, pos=$position)")
fun accept(visitor: IAstVisitor) {
origExpression?.accept(visitor)
indexNum?.accept(visitor)
indexVar?.accept(visitor)
}
fun accept(visitor: AstWalker, parent: Node) {
origExpression?.accept(visitor, this)
indexNum?.accept(visitor, this)
indexVar?.accept(visitor, this)
}
fun constIndex() = (index as? NumericLiteralValue)?.number?.toInt()
override fun toString(): String {
return("ArrayIndex($indexNum, $indexVar, pos=$position)")
}
fun constIndex() = indexNum?.number?.toInt()
infix fun isSameAs(other: ArrayIndex) = indexNum==other.indexNum && indexVar == other.indexVar
}
open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
@ -391,8 +434,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
@ -401,28 +444,17 @@ data class AssignTarget(var identifier: IdentifierReference?,
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
companion object {
fun fromExpr(expr: Expression): AssignTarget {
return when (expr) {
is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
else -> throw FatalAstException("invalid expression object $expr")
}
}
}
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
if(identifier!=null) {
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType { // TODO why does this have the extra 'stmt' scope parameter???
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 +462,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.arrayvar.nameInSource == arrayindexed!!.arrayvar.nameInSource)
arrayindexed!!.indexer isSameAs value.indexer
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) {
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
return x1!=null && x2!=null && x1==x2
if (this.arrayindexed != null && other.arrayindexed != null) {
if (this.arrayindexed!!.arrayvar.nameInSource == other.arrayindexed!!.arrayvar.nameInSource) {
val x1 = this.arrayindexed!!.indexer.constIndex()
val x2 = other.arrayindexed!!.indexer.constIndex()
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!!.arrayvar.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
@ -581,6 +637,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() {
override val name: String
override lateinit var parent: Node
override val asmGenInfo = AsmGenInfo()
companion object {
private var sequenceNumber = 1
@ -634,6 +691,7 @@ class Subroutine(override val name: String,
override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node
override val asmGenInfo = AsmGenInfo()
val scopedname: String by lazy { makeScopedName(name) }
override fun linkParents(parent: Node) {
@ -815,19 +873,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")
}
@ -866,11 +924,11 @@ class WhenStatement(var condition: Expression,
if(choice.values==null)
result.add(null to choice)
else {
val values = choice.values!!.map { it.constValue(program)?.number?.toInt() }
if(values.contains(null))
result.add(null to choice)
else
result.add(values.filterNotNull() to choice)
val values = choice.values!!.map {
val cv = it.constValue(program)
cv?.number?.toInt() ?: it.hashCode() // the hashcode is a nonsensical number but it avoids weird AST validation errors later
}
result.add(values to choice)
}
}
return result
@ -892,9 +950,15 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===statements)
statements = replacement
replacement.parent = this
val choiceValues = values
if(replacement is AnonymousScope && node===statements) {
statements = replacement
replacement.parent = this
} else if(choiceValues!=null && node in choiceValues) {
throw FatalAstException("cannot replace choice values")
} else {
throw FatalAstException("invalid replacement")
}
}
override fun toString(): String {
@ -911,6 +975,7 @@ class StructDecl(override val name: String,
override val position: Position): Statement(), INameScope {
override lateinit var parent: Node
override val asmGenInfo = AsmGenInfo()
override fun linkParents(parent: Node) {
this.parent = parent

View File

@ -15,9 +15,18 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
subroutineVariables.add(Pair(decl.name, decl))
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero.
decl.value = decl.zeroElementValue()
// a numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value
if(decl.allowInitializeWithZero)
{
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
decl.value = null
else
decl.value = decl.zeroElementValue()
}
}
return noModifications
}
@ -25,53 +34,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, assignment.definingScope()),
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, assignment.definingScope()),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
}
}
}
return noModifications
}
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear()
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
subroutineVariables.addAll(decls.map { Pair(it.name, it) })
val sub = scope.definingSubroutine()
if (sub != null) {
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing != null) {
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
conflicts = true
}
}
if (!conflicts) {
// move vardecls of the scope into the upper scope. Make sure the order remains the same!
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }.reversed()
return numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, initValue, it.position)
initValue.parent = assign
IAstModification.InsertFirst(assign, scope)
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
val replaceVardecls =numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, initValue, it.position)
initValue.parent = assign
IAstModification.ReplaceNode(it, assign, scope)
}
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
return replaceVardecls + moveVardeclsUp
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val firstDeclarations = mutableMapOf<String, VarDecl>()
for(decl in subroutineVariables) {
val existing = firstDeclarations[decl.first]
if(existing!=null && existing !== decl.second) {
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
} else {
firstDeclarations[decl.first] = decl.second
}
}
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
@ -93,9 +127,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
&& outerStatements[subroutineStmtIdx - 1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
return mods
}
@ -131,11 +164,13 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
return listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
parent
))
if(typecast.expression is IdentifierReference) {
return listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
parent
))
}
} else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
}
@ -143,4 +178,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

@ -28,7 +28,9 @@ data class CompilationOptions(val output: OutputType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean,
val compilationTarget: String?)
val noSysInit: Boolean) {
var slowCodegenWarnings = false
}
class CompilerException(message: String?) : Exception(message)

View File

@ -7,6 +7,7 @@ import prog8.ast.statements.Directive
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.optimizer.*
import prog8.optimizer.UnusedCodeRemover
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
@ -28,6 +29,7 @@ class CompilationResult(val success: Boolean,
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
compilationTarget: String,
outputDir: Path): CompilationResult {
var programName = ""
@ -48,6 +50,7 @@ fun compileProgram(filepath: Path,
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
@ -91,7 +94,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)
@ -106,9 +109,9 @@ private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program,
// 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
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math")
importer.importLibraryModule(programAst, "prog8lib")
importer.importLibraryModule(programAst, "prog8_lib")
errors.handle()
return Triple(programAst, compilerOptions, importedFiles)
}
@ -119,13 +122,12 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" }
as? Directive)?.args?.single()?.int ?: 0
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val zpType: ZeropageType =
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
@ -135,6 +137,12 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && CompilationTarget.instance.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
@ -142,20 +150,19 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
.map { it[0].int!!..it[1].int!! }
.toList()
var target = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%target" }
as? Directive)?.args?.single()?.name
when(target) {
C64Target.name -> CompilationTarget.instance = C64Target
Cx16Target.name -> CompilationTarget.instance = Cx16Target
null -> target = CompilationTarget.instance.name
else -> throw FatalAstException("invalid target")
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
System.err.println("invalid output type $outputType")
exitProcess(1)
}
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, target
zpType, zpReserved, floatsEnabled, noSysInit
)
}
@ -166,7 +173,8 @@ private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptio
errors.handle()
programAst.constantFold(errors)
errors.handle()
programAst.reorderStatements()
programAst.reorderStatements(errors)
errors.handle()
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
@ -182,14 +190,15 @@ private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements(errors)
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away:
val optsDone2 = programAst.splitBinaryExpressions()
val optsDone3 = programAst.optimizeStatements(errors)
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
errors.handle()
if (optsDone1 + optsDone2 == 0)
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
val remover = UnusedCodeRemover(errors)
val remover = UnusedCodeRemover(programAst, errors)
remover.visit(programAst)
remover.applyModifications()
errors.handle()
@ -201,9 +210,11 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
errors.handle()
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
val callGraph = CallGraph(programAst)
callGraph.checkRecursiveCalls(errors)
errors.handle()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
@ -214,9 +225,6 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
// printAst(programAst)
if(compilerOptions.compilationTarget!=null && compilerOptions.compilationTarget != CompilationTarget.instance.name)
throw AssemblyError("program's compilation target differs from configured target")
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
val assembly = CompilationTarget.instance.asmGenerator(
programAst,

View File

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

View File

@ -17,8 +17,6 @@ internal interface CompilationTarget {
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator
val asmForSystemReset: String
val initProcName: String?
companion object {
lateinit var instance: CompilationTarget
@ -35,8 +33,6 @@ internal object C64Target: CompilationTarget {
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
AsmGen(program, errors, zp, options, path)
override val asmForSystemReset = " sei | lda #14 | sta $1 | jmp (c64.RESET_VEC)"
override val initProcName = "c64.init_system"
}
internal object Cx16Target: CompilationTarget {
@ -48,6 +44,4 @@ internal object Cx16Target: CompilationTarget {
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
AsmGen(program, errors, zp, options, path)
override val asmForSystemReset = " sei | stz cx16.d1prb | jmp (cx16.RESET_VEC)"
override val initProcName = "cx16.init_system"
}

View File

@ -35,4 +35,5 @@ internal interface IMachineDefinition {
fun getFloatRomConst(number: Double): String?
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
fun launchEmulator(programName: String)
fun isRegularRAMaddress(address: Int): Boolean
}

View File

@ -37,28 +37,29 @@ internal object C64MachineDefinition: IMachineDefinition {
val mflpt5 = Mflpt5.fromNumber(number)
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
when {
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.ZERO" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "c64flt.FL_PIVAL"
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_N32768"
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FONE"
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRHLF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRTWO"
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_NEGHLF"
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "c64flt.FL_LOG2"
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "c64flt.FL_TENC"
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "c64flt.FL_NZMIL"
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FHALF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "c64flt.FL_LOGEB2"
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_PIHALF"
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_TWOPI"
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FR4"
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ZERO_const" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ONE_const" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "floats.FL_PIVAL"
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_N32768"
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FONE"
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRHLF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRTWO"
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_NEGHLF"
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "floats.FL_LOG2"
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "floats.FL_TENC"
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "floats.FL_NZMIL"
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FHALF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "floats.FL_LOGEB2"
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_PIHALF"
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_TWOPI"
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FR4"
else -> {
// attempt to correct for a few rounding issues
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
3.1415926536 -> return "c64flt.FL_PIVAL"
1.4142135624 -> return "c64flt.FL_SQRTWO"
0.7071067812 -> return "c64flt.FL_SQRHLF"
0.6931471806 -> return "c64flt.FL_LOG2"
3.1415926536 -> return "floats.FL_PIVAL"
1.4142135624 -> return "floats.FL_SQRTWO"
0.7071067812 -> return "floats.FL_SQRHLF"
0.6931471806 -> return "floats.FL_LOG2"
else -> {}
}
}
@ -68,7 +69,7 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "c64lib")
importer.importLibraryModule(program, "syslib")
}
override fun launchEmulator(programName: String) {
@ -88,6 +89,8 @@ internal object C64MachineDefinition: IMachineDefinition {
}
}
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
}
@ -112,12 +115,6 @@ internal object C64MachineDefinition: IMachineDefinition {
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
@ -129,8 +126,10 @@ internal object C64MachineDefinition: IMachineDefinition {
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) {
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
free.addAll(listOf(
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x16, 0x17, 0x18, 0x19, 0x1a,
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
@ -147,20 +146,20 @@ 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,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
))
}
if(options.zeropage!=ZeropageType.DONTUSE) {
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
} else {
// don't use the zeropage at all
free.clear()

View File

@ -1054,11 +1054,16 @@ object Petscii {
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
return text.map {
val petscii = lookup[it]
petscii?.toShort() ?: if(it=='\u0000')
0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
petscii?.toShort() ?: when (it) {
'\u0000' -> 0.toShort()
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(it.toInt() - 0x8000).toShort()
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
}
}
}
}
@ -1072,11 +1077,16 @@ object Petscii {
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
return text.map{
val screencode = lookup[it]
screencode?.toShort() ?: if(it=='\u0000')
0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
screencode?.toShort() ?: when (it) {
'\u0000' -> 0.toShort()
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(it.toInt() - 0x8000).toShort()
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
}
}
}
}

View File

@ -47,10 +47,10 @@ internal class AsmGen(private val program: Program,
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
internal val loopEndLabels = ArrayDeque<String>()
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
assemblyLines.clear()
@ -66,21 +66,27 @@ internal class AsmGen(private val program: Program,
block2asm(b)
footer()
if(optimize) {
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
}
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
if(optimize) {
assemblyLines.clear()
assemblyLines.addAll(outputFile.readLines())
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
}
return AssemblyProgram(program.name, outputDir)
}
private fun header() {
val ourName = this.javaClass.name
val cpu = when(CompilationTarget.instance.machine.cpu) {
@ -120,18 +126,14 @@ internal class AsmGen(private val program: Program,
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
out(" tsx")
out(" stx prog8_lib.orig_stackpointer")
if(!CompilationTarget.instance.initProcName.isNullOrEmpty())
out(" jsr ${CompilationTarget.instance.initProcName}")
if(!options.noSysInit)
out(" jsr ${CompilationTarget.instance.name}.init_system")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
out(" tsx")
out(" stx prog8_lib.orig_stackpointer")
if(!CompilationTarget.instance.initProcName.isNullOrEmpty())
out(" jsr ${CompilationTarget.instance.initProcName}")
if(!options.noSysInit)
out(" jsr ${CompilationTarget.instance.name}.init_system")
}
options.output == OutputType.RAW -> {
out("; ---- raw assembler program ----")
@ -139,33 +141,16 @@ internal class AsmGen(private val program: Program,
}
}
if (zeropage.exitProgramStrategy != Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
// disable shift-commodore charset switching and run/stop key
out(" lda #$80")
out(" lda #$80")
out(" sta 657\t; disable charset switching")
out(" lda #239")
out(" sta 808\t; disable run/stop key")
if(options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
out("""
; zeropage is clobbered so we need to reset the machine at exit
lda #>${CompilationTarget.instance.name}.reset_system
pha
lda #<${CompilationTarget.instance.name}.reset_system
pha""")
}
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block that has globals")
program.allBlocks().forEach {
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
out(" jsr ${it.name}.prog8_init_vars")
}
out(" clc")
when (zeropage.exitProgramStrategy) {
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
out(" jmp main.start\t; jump to program entrypoint")
}
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
out(" jsr main.start\t; call program entrypoint")
out(CompilationTarget.instance.asmForSystemReset)
}
}
out(" jmp main.start ; start program / force start proc to be included")
}
private fun footer() {
@ -214,11 +199,10 @@ internal class AsmGen(private val program: Program,
}
private fun assignInitialValueToVar(decl: VarDecl, variableName: List<String>) {
val variable = IdentifierReference(variableName, decl.position)
variable.linkParents(decl.parent)
val asmName = asmVariableName(variableName)
val asgn = AsmAssignment(
AsmAssignSource.fromAstSource(decl.value!!, program),
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, variable = variable),
AsmAssignSource.fromAstSource(decl.value!!, program, this),
AsmAssignTarget(TargetStorageKind.VARIABLE, program, this, decl.datatype, decl.definingSubroutine(), variableAsmName = asmName),
false, decl.position)
assignmentAsmGen.translateNormalAssignment(asgn)
}
@ -355,7 +339,10 @@ internal class AsmGen(private val program: Program,
out("\n; memdefs and kernel subroutines")
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
for(m in memvars) {
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
if(m.value is NumericLiteralValue)
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
else
out(" ${m.name} = ${asmVariableName((m.value as AddressOf).identifier)}")
}
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
for(sub in asmSubs) {
@ -423,10 +410,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")
@ -506,8 +500,14 @@ internal class AsmGen(private val program: Program,
}
}
internal fun asmSymbolName(name: String) = fixNameSymbols(name)
internal fun asmVariableName(name: String) = fixNameSymbols(name)
internal fun asmSymbolName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
internal fun asmVariableName(name: Iterable<String>) = fixNameSymbols(name.joinToString("."))
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
// returns if the pointer is already on the ZP itself or not (in which case SCRATCH_W1 is used as intermediary)
// returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary)
val sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program.namespace)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
@ -548,55 +548,69 @@ 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 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 -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
else {
val save = makeLabel("saveX")
saveRegisterLabels.push(save)
out("""
stx $save
jmp +
$save .byte 0
+""")
internal fun saveRegister(register: CpuRegister, dontUseStack: Boolean, scope: Subroutine?) {
if(dontUseStack) {
when (register) {
CpuRegister.A -> {
out(" sta _prog8_regsaveA")
if (scope != null)
scope.asmGenInfo.usedRegsaveA = true
}
CpuRegister.X -> {
out(" stx _prog8_regsaveX")
if (scope != null)
scope.asmGenInfo.usedRegsaveX = true
}
CpuRegister.Y -> {
out(" sty _prog8_regsaveY")
if (scope != null)
scope.asmGenInfo.usedRegsaveY = true
}
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
else {
val save = makeLabel("saveY")
out("""
sty $save
jmp +
$save .byte 0
+""")
} else {
when (register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
else {
out(" stx _prog8_regsaveX")
if (scope != null)
scope.asmGenInfo.usedRegsaveX = true
}
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
else {
out(" sty _prog8_regsaveY")
if (scope != null)
scope.asmGenInfo.usedRegsaveY = true
}
}
}
}
}
internal fun restoreRegister(register: CpuRegister) {
when(register) {
CpuRegister.A -> out(" pla")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
else {
val save = saveRegisterLabels.pop()
out(" ldx $save")
}
internal fun restoreRegister(register: CpuRegister, dontUseStack: Boolean) {
if(dontUseStack) {
when (register) {
CpuRegister.A -> out(" sta _prog8_regsaveA")
CpuRegister.X -> out(" ldx _prog8_regsaveX")
CpuRegister.Y -> out(" ldy _prog8_regsaveY")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
else {
val save = saveRegisterLabels.pop()
out(" ldy $save")
} else {
when (register) {
CpuRegister.A -> out(" pla")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
else out(" ldx _prog8_regsaveX")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
else out(" ldy _prog8_regsaveY")
}
}
}
@ -616,19 +630,22 @@ $save .byte 0
is FunctionCallStatement -> {
val functionName = stmt.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
if(builtinFunc!=null) {
if (builtinFunc != null) {
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
} else {
functioncallAsmGen.translateFunctionCall(stmt)
// discard any results from the stack:
val sub = stmt.target.targetSubroutine(program.namespace)!!
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
functioncallAsmGen.translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
// discard any results from the stack:
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")
}
}
if(preserveStatusRegisterAfterCall)
out(" plp\t; restore status flags from call")
}
}
is Assignment -> assignmentAsmGen.translate(stmt)
@ -659,101 +676,75 @@ $save .byte 0
register: CpuRegister,
addOneExtra: Boolean=false) {
val reg = register.toString().toLowerCase()
val index = expr.arrayspec.index
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * elementDt.memorySize() + if(addOneExtra) 1 else 0
val indexnum = expr.indexer.constIndex()
if(indexnum!=null) {
val indexValue = indexnum * elementDt.memorySize() + if(addOneExtra) 1 else 0
out(" ld$reg #$indexValue")
return
}
val indexName = asmVariableName(expr.indexer.indexVar!!)
if(addOneExtra) {
// add 1 to the result
if (index is IdentifierReference) {
val indexName = asmVariableName(index)
when(elementDt) {
in ByteDatatypes -> {
out(" ldy $indexName | iny")
when(register) {
CpuRegister.A -> out(" tya")
CpuRegister.X -> out(" tyx")
CpuRegister.Y -> {}
}
when(elementDt) {
in ByteDatatypes -> {
out(" ldy $indexName | iny")
when(register) {
CpuRegister.A -> out(" tya")
CpuRegister.X -> out(" tyx")
CpuRegister.Y -> {}
}
in WordDatatypes -> {
out(" lda $indexName | sec | rol a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
sec
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
else -> throw AssemblyError("weird dt")
}
}
else {
expressionsAsmGen.translateExpression(index)
out("""
inc P8ESTACK_LO,x
bne +
inc P8ESTACK_HI,x
+""")
when(register) {
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
in WordDatatypes -> {
out(" lda $indexName | sec | rol a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
sec
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
else -> throw AssemblyError("weird dt")
}
} else {
if (index is IdentifierReference) {
val indexName = asmVariableName(index)
when(elementDt) {
in ByteDatatypes -> out(" ld$reg $indexName")
in WordDatatypes -> {
out(" lda $indexName | asl a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
when(elementDt) {
in ByteDatatypes -> out(" ld$reg $indexName")
in WordDatatypes -> {
out(" lda $indexName | asl a")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
clc
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
out("""
lda $indexName
asl a
asl a
clc
adc $indexName""")
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> out(" tax")
CpuRegister.Y -> out(" tay")
}
else -> throw AssemblyError("weird dt")
}
}
else {
expressionsAsmGen.translateExpression(index)
when(register) {
CpuRegister.A -> out(" inx | lda P8ESTACK_LO,x")
CpuRegister.X -> out(" inx | lda P8ESTACK_LO,x | tax")
CpuRegister.Y -> out(" inx | ldy P8ESTACK_LO,x")
}
else -> throw AssemblyError("weird dt")
}
}
}
@ -761,11 +752,14 @@ $save .byte 0
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateExpression(indexer: ArrayIndex) =
expressionsAsmGen.translateExpression(indexer)
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
internal fun translateFunctionCall(functionCall: FunctionCall) =
functioncallAsmGen.translateFunctionCall(functionCall)
internal fun translateFunctionCall(functionCall: FunctionCall, preserveStatusRegisterAfterCall: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCall, preserveStatusRegisterAfterCall)
internal fun translateNormalAssignment(assign: AsmAssignment) =
assignmentAsmGen.translateNormalAssignment(assign)
@ -787,9 +781,33 @@ $save .byte 0
out("${sub.name}\t.proc")
zeropagevars2asm(sub.statements)
memdefs2asm(sub.statements)
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
if(sub.name=="start" && sub.definingBlock().name=="main") {
out("; program startup initialization")
out(" cld")
program.allBlocks().forEach {
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
out(" jsr ${it.name}.prog8_init_vars")
}
out("""
tsx
stx prog8_lib.orig_stackpointer ; required for func_exit
ldx #255 ; init estack ptr
clv
clc""")
}
out("; statements")
sub.statements.forEach{ translate(it) }
out("; variables")
out("; register saves")
if(sub.asmGenInfo.usedRegsaveA)
out("_prog8_regsaveA .byte 0")
if(sub.asmGenInfo.usedRegsaveX)
out("_prog8_regsaveX .byte 0")
if(sub.asmGenInfo.usedRegsaveY)
out("_prog8_regsaveY .byte 0")
vardecls2asm(sub.statements)
out(" .pend\n")
}
@ -821,47 +839,32 @@ $save .byte 0
}
private fun translate(stmt: IfStatement) {
when {
stmt.elsepart.containsNoCodeNorVars() -> {
// empty else
expressionsAsmGen.translateExpression(stmt.condition)
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
val endLabel = makeLabel("if_end")
out(" beq $endLabel")
translate(stmt.truepart)
out(endLabel)
}
stmt.truepart.containsNoCodeNorVars() -> {
// empty true part
expressionsAsmGen.translateExpression(stmt.condition)
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
val endLabel = makeLabel("if_end")
out(" bne $endLabel")
translate(stmt.elsepart)
out(endLabel)
}
else -> {
expressionsAsmGen.translateExpression(stmt.condition)
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
val elseLabel = makeLabel("if_else")
val endLabel = makeLabel("if_end")
out(" beq $elseLabel")
translate(stmt.truepart)
out(" jmp $endLabel")
out(elseLabel)
translate(stmt.elsepart)
out(endLabel)
}
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
if (stmt.elsepart.containsNoCodeNorVars()) {
// empty else
val endLabel = makeLabel("if_end")
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.truepart)
out(endLabel)
}
else {
// both true and else parts
val elseLabel = makeLabel("if_else")
val endLabel = makeLabel("if_end")
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, elseLabel)
translate(stmt.truepart)
out(" jmp $endLabel")
out(elseLabel)
translate(stmt.elsepart)
out(endLabel)
}
}
private fun translateTestStack(dataType: DataType) {
when(dataType) {
in ByteDatatypes -> out(" inx | lda P8ESTACK_LO,x")
in WordDatatypes -> out(" inx | lda P8ESTACK_LO,x | ora P8ESTACK_HI,x")
DataType.FLOAT -> throw AssemblyError("conditional value should be an integer (boolean)")
else -> throw AssemblyError("non-numerical dt")
}
private fun checkBooleanExpression(condition: Expression) {
if(condition !is BinaryExpression || condition.operator !in comparisonOperators)
throw AssemblyError("expected boolean expression $condition")
}
private fun translate(stmt: RepeatLoop) {
@ -929,6 +932,8 @@ $save .byte 0
}
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
if(constIterations==0)
return
// note: A/Y must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter")
out("""
@ -958,8 +963,12 @@ $counterVar .word 0""")
}
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
if(constIterations==0)
return
// note: A must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter")
if(constIterations==null)
out(" beq $endLabel")
out("""
sta $counterVar
$repeatLabel""")
@ -980,25 +989,13 @@ $counterVar .byte 0""")
}
private fun translate(stmt: WhileLoop) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend")
loopEndLabels.push(endLabel)
out(whileLabel)
expressionsAsmGen.translateExpression(stmt.condition)
val conditionDt = stmt.condition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" inx | lda P8ESTACK_LO,x | beq $endLabel")
} else {
out("""
inx
lda P8ESTACK_LO,x
bne +
lda P8ESTACK_HI,x
beq $endLabel
+ """)
}
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.body)
out(" jmp $whileLabel")
out(endLabel)
@ -1006,26 +1003,14 @@ $counterVar .byte 0""")
}
private fun translate(stmt: UntilLoop) {
checkBooleanExpression(stmt.condition) // we require the condition to be of the form 'x <comparison> <value>'
val booleanCondition = stmt.condition as BinaryExpression
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
out(repeatLabel)
translate(stmt.body)
expressionsAsmGen.translateExpression(stmt.untilCondition)
val conditionDt = stmt.untilCondition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" inx | lda P8ESTACK_LO,x | beq $repeatLabel")
} else {
out("""
inx
lda P8ESTACK_LO,x
bne +
lda P8ESTACK_HI,x
beq $repeatLabel
+ """)
}
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, repeatLabel)
out(endLabel)
loopEndLabels.pop()
}
@ -1065,6 +1050,7 @@ $counterVar .byte 0""")
}
}
}
out(" jmp $endLabel")
for(choiceBlock in choiceBlocks) {
out(choiceBlock.first)
translate(choiceBlock.second)
@ -1187,4 +1173,40 @@ $counterVar .byte 0""")
val assembly = asm.assembly.trimEnd().trimStart('\n')
assemblyLines.add(assembly)
}
internal fun signExtendStackLsb(valueDt: DataType) {
// sign extend signed byte on stack to signed word
when(valueDt) {
DataType.UBYTE -> {
out(" lda #0 | sta P8ESTACK_HI+1,x")
}
DataType.BYTE -> {
out("""
lda P8ESTACK_LO+1,x
ora #$7f
bmi +
lda #0
+ sta P8ESTACK_HI+1,x""")
}
else -> throw AssemblyError("need byte type")
}
}
internal fun signExtendVariableLsb(asmvar: String, valueDt: DataType) {
// sign extend signed byte in a word variable
when(valueDt) {
DataType.UBYTE -> {
out(" lda #0 | sta $asmvar+1")
}
DataType.BYTE -> {
out("""
lda $asmvar+1
ora #$7f
bmi +
lda #0
+ sta $asmvar+1""")
}
else -> throw AssemblyError("need byte type")
}
}
}

View File

@ -152,7 +152,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
(seventh.startsWith("jsr c64flt.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
val nineth = pair[8].value.trimStart()
val tenth = pair[9].value.trimStart()
@ -163,7 +163,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
(fourteenth.startsWith("jsr c64flt.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
// identical float init
@ -179,6 +179,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
}
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// TODO not sure if this is correct in all situations....:
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
@ -195,8 +196,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx "))
) {
val firstLoc = first.substring(4)
val secondLoc = second.substring(4)
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
}

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
@ -153,8 +159,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror2_array_ub")
}
is DirectMemoryRead -> {
@ -176,8 +182,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror2_array_uw")
}
is IdentifierReference -> {
@ -198,8 +204,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror_array_ub")
}
is DirectMemoryRead -> {
@ -228,8 +234,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror_array_uw")
}
is IdentifierReference -> {
@ -250,8 +256,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol2_array_ub")
}
is DirectMemoryRead -> {
@ -273,8 +279,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol2_array_uw")
}
is IdentifierReference -> {
@ -295,8 +301,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol_array_ub")
}
is DirectMemoryRead -> {
@ -325,8 +331,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol_array_uw")
}
is IdentifierReference -> {
@ -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,30 +383,42 @@ 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")
}
}
private fun funcStrlen(fcall: IFunctionCall) {
val name = asmgen.asmVariableName(fcall.args[0] as IdentifierReference)
asmgen.out("""
lda #<$name
ldy #>$name
jsr prog8_lib.strlen
sta P8ESTACK_LO,x
dex""")
val type = fcall.args[0].inferType(program)
when {
type.istype(DataType.STR) -> asmgen.out("""
lda #<$name
ldy #>$name
jsr prog8_lib.strlen
sta P8ESTACK_LO,x
dex""")
type.istype(DataType.UWORD) -> asmgen.out("""
lda $name
ldy $name+1
jsr prog8_lib.strlen
sta P8ESTACK_LO,x
dex""")
else -> throw AssemblyError("strlen requires str or uword arg")
}
}
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 +444,319 @@ 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 arrayVarName1 = asmgen.asmVariableName(first.arrayvar)
val arrayVarName2 = asmgen.asmVariableName(second.arrayvar)
val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT)
val firstNum = first.indexer.indexNum
val firstVar = first.indexer.indexVar
val secondNum = second.indexer.indexNum
val secondVar = second.indexer.indexVar
if(firstNum!=null && secondNum!=null) {
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondNum)
return
} else if(firstVar!=null && secondVar!=null) {
swapArrayValues(elementDt, arrayVarName1, firstVar, arrayVarName2, secondVar)
return
} else if(firstNum!=null && secondVar!=null) {
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondVar)
return
} else if(firstVar!=null && secondNum!=null) {
swapArrayValues(elementDt, arrayVarName1, firstVar, arrayVarName2, secondNum)
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, expr.definingSubroutine(), variableAsmName = asmgen.asmVariableName(expr))
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, expr.definingSubroutine(), array = expr)
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, expr.definingSubroutine(), 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, asmgen, datatype),
targetFromExpr(first, datatype),
false, first.position
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, 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 swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) {
val index1 = indexValue1.number.toInt() * elementDt.memorySize()
val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
lda $arrayVarName1 + $index1
pha
ldy $idxAsmName2
lda $arrayVarName2,y
sta $arrayVarName1 + $index1
pla
sta $arrayVarName2,y
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
lda $arrayVarName1 + $index1
pha
lda $idxAsmName2
asl a
tay
lda $arrayVarName2,y
sta $arrayVarName1 + $index1
pla
sta $arrayVarName2,y
lda $arrayVarName1 + $index1+1
pha
lda $arrayVarName2+1,y
sta $arrayVarName1 + $index1+1
pla
sta $arrayVarName2+1,y
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1
lda #>(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1+1
lda #>$arrayVarName1
sta P8ZP_SCRATCH_W1+1
lda $idxAsmName2
asl a
asl a
clc
adc $idxAsmName2
adc #<$arrayVarName1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val idxAsmName1 = asmgen.asmVariableName(indexName1)
val index2 = indexValue2.number.toInt() * elementDt.memorySize()
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
lda $arrayVarName2 + $index2
pha
ldy $idxAsmName1
lda $arrayVarName1,y
sta $arrayVarName2 + $index2
pla
sta $arrayVarName1,y
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
lda $arrayVarName2 + $index2
pha
lda $idxAsmName1
asl a
tay
lda $arrayVarName1,y
sta $arrayVarName2 + $index2
pla
sta $arrayVarName1,y
lda $arrayVarName2 + $index2+1
pha
lda $arrayVarName1+1,y
sta $arrayVarName2 + $index2+1
pla
sta $arrayVarName1+1,y
""")
}
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}+$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 funcAbs(fcall: IFunctionCall, func: FSignature) {
@ -442,7 +765,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

@ -610,8 +610,8 @@ $endLabel""")
}
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 target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine(), variableAsmName=asmgen.asmVariableName(stmt.loopVar))
val src = AsmAssignSource.fromAstSource(range.from, program, asmgen).adjustSignedUnsigned(target)
val assign = AsmAssignment(src, target, false, range.position)
asmgen.translateNormalAssignment(assign)
}

View File

@ -1,6 +1,7 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
@ -13,13 +14,13 @@ import prog8.compiler.target.c64.codegen.assignment.*
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCall(stmt: IFunctionCall) {
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
// 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() || sub.regXasParam()
if(saveX)
asmgen.saveRegister(CpuRegister.X)
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine())
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
@ -57,8 +58,14 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
asmgen.out(" jsr $subName")
if(preserveStatusRegisterAfterCall) {
asmgen.out(" php\t; save status flags from call")
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
// must take care of popping this value again at the end!
}
if(saveX)
asmgen.restoreRegister(CpuRegister.X)
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
@ -148,11 +155,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val scopedParamVar = (sub.scopedname+"."+parameter.value.name).split(".")
val identifier = IdentifierReference(scopedParamVar, sub.position)
identifier.linkParents(value.parent)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, variable = identifier)
val source = AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(tgt)
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, sub, variableAsmName = varName)
val source = AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(tgt)
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
asmgen.translateNormalAssignment(asgn)
}
@ -170,13 +175,22 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
val requiredDt = parameter.value.type
if(requiredDt!=valueDt) {
if(valueDt largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types")
}
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
if(requiredDt!=valueDt)
asmgen.signExtendStackLsb(valueDt)
}
statusflag!=null -> {
if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
@ -216,15 +230,30 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
else -> {
// via register or register pair
val target = AsmAssignTarget.fromRegisters(register!!, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) {
val addr = AddressOf(value as IdentifierReference, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program).adjustDataTypeToTarget(target)
} else {
AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(target)
val target = AsmAssignTarget.fromRegisters(register!!, sub, program, asmgen)
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
val scratchTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UBYTE, sub, scratchVar)
val source = AsmAssignSource.fromAstSource(value, program, asmgen)
asmgen.translateNormalAssignment(AsmAssignment(source, scratchTarget, false, value.position))
asmgen.signExtendVariableLsb(scratchVar, valueDt)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, DataType.UWORD, scratchVar)
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
}
else {
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
}
}
}

View File

@ -15,6 +15,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed
val scope = stmt.definingSubroutine()
when {
targetIdent!=null -> {
val what = asmgen.asmVariableName(targetIdent)
@ -33,7 +34,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")
}
@ -69,67 +70,64 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.identifier)
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
when(index) {
is NumericLiteralValue -> {
val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
else
asmgen.out("""
lda $asmArrayvarname+$indexValue
bne +
dec $asmArrayvarname+$indexValue+1
if(targetArrayIdx.indexer.indexNum!=null) {
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
else
asmgen.out("""
lda $asmArrayvarname+$indexValue
bne +
dec $asmArrayvarname+$indexValue+1
+ dec $asmArrayvarname+$indexValue
""")
}
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")
}
else -> throw AssemblyError("need numeric type")
}
}
else -> {
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
}
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
else
asmgen.out("""
lda $asmArrayvarname,x
bne +
dec $asmArrayvarname+1,x
+ dec $asmArrayvarname
""")
}
DataType.FLOAT -> {
asmgen.out("""
ldy #>$asmArrayvarname
clc
adc #<$asmArrayvarname
bcc +
iny
+ jsr c64flt.inc_var_f""")
}
else -> throw AssemblyError("weird array elt dt")
DataType.FLOAT -> {
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
asmgen.restoreRegister(CpuRegister.X)
else -> throw AssemblyError("need numeric type")
}
}
else
{
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X, false, scope)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
}
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
else
asmgen.out("""
lda $asmArrayvarname,x
bne +
dec $asmArrayvarname+1,x
+ dec $asmArrayvarname
""")
}
DataType.FLOAT -> {
asmgen.out("""
ldy #>$asmArrayvarname
clc
adc #<$asmArrayvarname
bcc +
iny
+ jsr floats.inc_var_f""")
}
else -> throw AssemblyError("weird array elt dt")
}
asmgen.restoreRegister(CpuRegister.X, false)
}
}
else -> throw AssemblyError("weird target type ${stmt.target}")
}
}
}

View File

@ -6,6 +6,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.Subroutine
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.AsmGen
@ -29,10 +30,11 @@ internal enum class SourceStorageKind {
}
internal class AsmAssignTarget(val kind: TargetStorageKind,
program: Program,
asmgen: AsmGen,
private val program: Program,
private val asmgen: AsmGen,
val datatype: DataType,
val variable: IdentifierReference? = null,
val scope: Subroutine?,
private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryWrite? = null,
val register: RegisterOrPair? = null,
@ -40,20 +42,16 @@ 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 asmVarname by lazy {
if(variable!=null)
asmgen.asmVariableName(variable)
val constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array!!.identifier)
}
asmgen.asmVariableName(array.arrayvar)
lateinit var origAssign: AsmAssignment
init {
if(variable!=null && vardecl.type == VarDeclType.CONST)
throw AssemblyError("can't assign to a constant")
if(register!=null && datatype !in IntegerDatatypes)
throw AssemblyError("register must be integer type")
}
@ -62,29 +60,30 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
val dt = inferType(program, assign).typeOrElse(DataType.STRUCT)
when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, variable=identifier, origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, array = arrayindexed, origAstTarget = this)
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, memory = memoryAddress, origAstTarget = this)
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine(), memory = memoryAddress, origAstTarget = this)
else -> throw AssemblyError("weird target")
}
}
fun fromRegisters(registers: RegisterOrPair, program: Program, asmgen: AsmGen): AsmAssignTarget =
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
when(registers) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, register = registers)
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, register = registers)
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
}
}
}
internal class AsmAssignSource(val kind: SourceStorageKind,
private val program: Program,
private val asmgen: AsmGen,
val datatype: DataType,
val variable: IdentifierReference? = null,
private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryRead? = null,
val register: CpuRegister? = null,
@ -93,53 +92,73 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
)
{
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 constArrayIndexValue by lazy { array?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.arrayvar)
companion object {
fun fromAstSource(value: Expression, program: Program): AsmAssignSource {
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
val cv = value.constValue(program)
if(cv!=null)
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, cv.type, number = cv)
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
return when(value) {
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, value.type, number = cv)
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, value.type, number = cv)
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.VARIABLE, program, dt, variable = value)
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = asmgen.asmVariableName(value))
}
is DirectMemoryRead -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, DataType.UBYTE, memory = value)
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
}
is ArrayIndexedExpression -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.ARRAY, program, dt, array = value)
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
}
else -> {
if(value is FunctionCall) {
// functioncall.
val asmSub = value.target.targetStatement(program.namespace)
if(asmSub is Subroutine && asmSub.isAsmSubroutine) {
when (asmSub.asmReturnvaluesRegisters.count { rr -> rr.registerOrPair!=null }) {
0 -> throw AssemblyError("can't translate zero return values in assignment")
1 -> {
// assignment generation itself must make sure the status register is correct after the subroutine call, if status register is involved!
val reg = asmSub.asmReturnvaluesRegisters.single { rr->rr.registerOrPair!=null }.registerOrPair!!
val dt = when(reg) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> DataType.UBYTE
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY -> DataType.UWORD
}
return AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
}
else -> throw AssemblyError("can't translate multiple return values in assignment")
}
}
}
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, dt, expression = value)
return AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
}
}
}
}
fun getAstValue(): Expression = when(kind) {
SourceStorageKind.LITERALNUMBER -> number!!
SourceStorageKind.VARIABLE -> variable!!
SourceStorageKind.ARRAY -> array!!
SourceStorageKind.MEMORY -> memory!!
SourceStorageKind.EXPRESSION -> expression!!
SourceStorageKind.REGISTER -> throw AssemblyError("cannot get a register source as Ast node")
SourceStorageKind.STACK -> throw AssemblyError("cannot get a stack source as Ast node")
}
fun withAdjustedDt(newType: DataType) =
AsmAssignSource(kind, program, newType, variable, array, memory, register, number, expression)
fun adjustDataTypeToTarget(target: AsmAssignTarget): AsmAssignSource {
fun adjustSignedUnsigned(target: AsmAssignTarget): AsmAssignSource {
// allow some signed/unsigned relaxations
fun withAdjustedDt(newType: DataType) =
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
if(target.datatype!=datatype) {
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
return withAdjustedDt(target.datatype)
@ -160,6 +179,9 @@ internal class AsmAssignment(val source: AsmAssignSource,
init {
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" }
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
require(source.datatype.memorySize() <= target.datatype.memorySize()) {
"source storage size must be less or equal to target datatype storage size"
}
}
}

View File

@ -8,16 +8,17 @@ 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)
val source = AsmAssignSource.fromAstSource(assignment.value, program).adjustDataTypeToTarget(target)
val source = AsmAssignSource.fromAstSource(assignment.value, program, asmgen).adjustSignedUnsigned(target)
val assign = AsmAssignment(source, target, assignment.isAugmentable, assignment.position)
target.origAssign = assign
@ -33,7 +34,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
SourceStorageKind.LITERALNUMBER -> {
// simple case: assign a constant number
val num = assign.source.number!!.number
when (assign.source.datatype) {
when (assign.target.datatype) {
DataType.UBYTE, DataType.BYTE -> assignConstantByte(assign.target, num.toShort())
DataType.UWORD, DataType.WORD -> assignConstantWord(assign.target, num.toInt())
DataType.FLOAT -> assignConstantFloat(assign.target, num.toDouble())
@ -42,30 +43,29 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
SourceStorageKind.VARIABLE -> {
// simple case: assign from another variable
val variable = assign.source.variable!!
when (assign.source.datatype) {
val variable = assign.source.asmVarname
when (assign.target.datatype) {
DataType.UBYTE, DataType.BYTE -> assignVariableByte(assign.target, variable)
DataType.UWORD, DataType.WORD -> assignVariableWord(assign.target, variable)
DataType.FLOAT -> assignVariableFloat(assign.target, variable)
in PassByReferenceDatatypes -> assignAddressOf(assign.target, variable)
DataType.STR -> assignVariableString(assign.target, variable)
else -> throw AssemblyError("unsupported assignment target type ${assign.target.datatype}")
}
}
SourceStorageKind.ARRAY -> {
val value = assign.source.array!!
val elementDt = assign.source.datatype
val index = value.arrayspec.index
val arrayVarName = asmgen.asmVariableName(value.identifier)
if (index is NumericLiteralValue) {
val arrayVarName = asmgen.asmVariableName(value.arrayvar)
if (value.indexer.indexNum!=null) {
// constant array index value
val indexValue = index.number.toInt() * elementDt.memorySize()
val indexValue = value.indexer.constIndex()!! * elementDt.memorySize()
when (elementDt) {
in ByteDatatypes ->
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | dex")
in WordDatatypes ->
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
DataType.FLOAT ->
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float")
else ->
throw AssemblyError("weird array type")
}
@ -87,7 +87,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")
@ -113,31 +113,46 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
SourceStorageKind.EXPRESSION -> {
val value = assign.source.expression!!
when(value) {
is AddressOf -> assignAddressOf(assign.target, value.identifier)
when(val value = assign.source.expression!!) {
is AddressOf -> {
val sourceName = value.identifier.firstStructVarName(program.namespace) ?: asmgen.asmVariableName(value.identifier)
assignAddressOf(assign.target, sourceName)
}
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
is IdentifierReference -> throw AssemblyError("source kind should have been variable")
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, assign)
// is FunctionCall -> {
// if (assign.target.kind == TargetStorageKind.STACK) {
// asmgen.translateExpression(value)
// assignStackValue(assign.target)
// } else {
// val functionName = value.target.nameInSource.last()
// val builtinFunc = BuiltinFunctions[functionName]
// if (builtinFunc != null) {
// println("!!!!BUILTIN-FUNCCALL target=${assign.target.kind} $value") // TODO optimize certain functions?
// }
// asmgen.translateExpression(value)
// assignStackValue(assign.target)
// }
// }
is FunctionCall -> {
if(value.target.targetSubroutine(program.namespace)?.isAsmSubroutine==true) {
// handle asmsub functioncalls specifically, without shoving stuff on the estack
val sub = value.target.targetSubroutine(program.namespace)!!
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any { it.statusflag != null }
asmgen.translateFunctionCall(value, preserveStatusRegisterAfterCall)
when((sub.asmReturnvaluesRegisters.single { it.registerOrPair!=null }).registerOrPair) {
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
else -> throw AssemblyError("should be just one register byte result value")
}
if(preserveStatusRegisterAfterCall)
asmgen.out(" plp\t; restore status flags from call")
} else {
// regular subroutine, return values are (for now) always done via the stack... TODO optimize this
asmgen.translateExpression(value)
if(assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
assignStackValue(assign.target)
}
}
else -> {
// everything else just evaluate via the stack.
asmgen.translateExpression(value)
if(assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
assignStackValue(assign.target)
}
}
@ -155,9 +170,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
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)
if(targetDt in WordDatatypes) {
if(valueDt==DataType.UBYTE) {
assignVariableUByteIntoWord(target, value)
return
}
if(valueDt==DataType.BYTE) {
assignVariableByteIntoWord(target, value)
return
}
}
@ -188,7 +207,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is FunctionCall -> {}
else -> {
// TODO optimize the others further?
println("warning: slow stack evaluation used for typecast: into $targetDt at ${value.position}")
if(this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for typecast: into $targetDt at ${value.position}")
}
}
@ -217,9 +237,21 @@ 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
""")
}
DataType.STR -> {
asmgen.out("""
lda #<${target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${target.asmVarname}
sta P8ZP_SCRATCH_W1+1
inx
lda P8ESTACK_HI,x
tay
lda P8ESTACK_LO,x
jsr prog8_lib.strcpy""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
}
}
@ -228,66 +260,60 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
storeByteViaRegisterAInMemoryAddress("P8ESTACK_LO,x", target.memory!!)
}
TargetStorageKind.ARRAY -> {
val index = target.array!!.arrayspec.index
when {
target.constArrayIndexValue!=null -> {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
}
in WordDatatypes -> {
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta ${target.asmVarname}+$scaledIdx
lda P8ESTACK_HI,x
sta ${target.asmVarname}+$scaledIdx+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
jsr c64flt.pop_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
}
}
index is IdentifierReference -> {
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname},y")
}
DataType.UWORD, DataType.WORD -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta ${target.asmVarname},y
lda P8ESTACK_HI,x
sta ${target.asmVarname}+1,y
""")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
+ jsr c64flt.pop_float""")
}
else -> throw AssemblyError("weird dt")
in WordDatatypes -> {
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta ${target.asmVarname}+$scaledIdx
lda P8ESTACK_HI,x
sta ${target.asmVarname}+$scaledIdx+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
jsr floats.pop_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
}
else -> {
asmgen.translateExpression(index)
asmgen.out(" inx | lda P8ESTACK_LO,x")
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
}
else
{
target.array!!
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname},y")
}
DataType.UWORD, DataType.WORD -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta ${target.asmVarname},y
lda P8ESTACK_HI,x
sta ${target.asmVarname}+1,y
""")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
asmgen.out("""
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
+ jsr floats.pop_float""")
}
else -> throw AssemblyError("weird dt")
}
}
}
@ -318,20 +344,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("."))
}
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
@ -356,19 +369,54 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
TargetStorageKind.STACK -> {
val srcname = asmgen.asmVariableName(name)
asmgen.out("""
lda #<$srcname
lda #<$sourceName
sta P8ESTACK_LO,x
lda #>$srcname
lda #>$sourceName
sta P8ESTACK_HI,x
dex""")
}
}
}
private fun assignVariableWord(target: AsmAssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmVariableName(variable)
private fun assignVariableString(target: AsmAssignTarget, sourceName: String) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
when(target.datatype) {
DataType.UWORD -> {
asmgen.out("""
lda #<$sourceName
sta ${target.asmVarname}
lda #>$sourceName
sta ${target.asmVarname}+1
""")
}
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<${target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${target.asmVarname}
sta P8ZP_SCRATCH_W1+1
lda #<$sourceName
ldy #>$sourceName
jsr prog8_lib.strcpy""")
}
else -> throw AssemblyError("assign string to incompatible variable type")
}
}
TargetStorageKind.STACK -> {
asmgen.out("""
lda #<$sourceName
sta P8ESTACK_LO,x
lda #>$sourceName+1
sta P8ESTACK_HI,x
dex""")
}
else -> throw AssemblyError("string-assign to weird target")
}
}
private fun assignVariableWord(target: AsmAssignTarget, sourceName: String) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
@ -382,73 +430,66 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
throw AssemblyError("no asm gen for assign wordvar $sourceName to memory ${target.memory}")
}
TargetStorageKind.ARRAY -> {
val index = target.array!!.arrayspec.index
when {
target.constArrayIndexValue!=null -> {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
in WordDatatypes -> {
asmgen.out("""
lda $sourceName
sta ${target.asmVarname}+$scaledIdx
lda $sourceName+1
sta ${target.asmVarname}+$scaledIdx+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
jsr c64flt.copy_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
target.array!!
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
}
index is IdentifierReference -> {
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
}
DataType.UWORD, DataType.WORD -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out("""
lda $sourceName
sta ${target.asmVarname},y
lda $sourceName+1
sta ${target.asmVarname}+1,y
""")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
asmgen.out("""
ldy #<$sourceName
sty P8ZP_SCRATCH_W1
ldy #>$sourceName
sty P8ZP_SCRATCH_W1+1
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
+ jsr c64flt.copy_float""")
}
else -> throw AssemblyError("weird dt")
in WordDatatypes -> {
asmgen.out("""
lda $sourceName
sta ${target.asmVarname}+$scaledIdx
lda $sourceName+1
sta ${target.asmVarname}+$scaledIdx+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
jsr floats.copy_float
""")
}
else -> throw AssemblyError("weird target variable type ${target.datatype}")
}
else -> {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | lda $sourceName+1 | sta P8ESTACK_HI,x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda P8ESTACK_LO,x")
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
}
else
{
when(target.datatype) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
}
DataType.UWORD, DataType.WORD -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out("""
lda $sourceName
sta ${target.asmVarname},y
lda $sourceName+1
sta ${target.asmVarname}+1,y
""")
}
DataType.FLOAT -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.A)
asmgen.out("""
ldy #<$sourceName
sty P8ZP_SCRATCH_W1
ldy #>$sourceName
sty P8ZP_SCRATCH_W1+1
ldy #>${target.asmVarname}
clc
adc #<${target.asmVarname}
bcc +
iny
+ jsr floats.copy_float""")
}
else -> throw AssemblyError("weird dt")
}
}
}
@ -471,8 +512,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignVariableFloat(target: AsmAssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmVariableName(variable)
private fun assignVariableFloat(target: AsmAssignTarget, sourceName: String) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
@ -489,25 +529,31 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
// TODO optimize this, but the situation doesn't occur very often
// if(target.constArrayIndexValue!=null) {
// TODO("const index ${target.constArrayIndexValue}")
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
// TODO("array[var] ${target.constArrayIndexValue}")
// }
val index = target.array!!.arrayspec.index
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
asmgen.translateExpression(index)
asmgen.out(" lda #<${target.asmVarname} | ldy #>${target.asmVarname} | jsr c64flt.pop_float_to_indexed_var")
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1""")
if(target.array!!.indexer.indexNum!=null) {
val index = target.array.indexer.constIndex()!!
asmgen.out(" lda #$index")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float")
}
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")
}
}
private fun assignVariableByte(target: AsmAssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmVariableName(variable)
private fun assignVariableByte(target: AsmAssignTarget, sourceName: String) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
@ -519,22 +565,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!)
}
TargetStorageKind.ARRAY -> {
val index = target.array!!.arrayspec.index
when {
target.constArrayIndexValue!=null -> {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
index is IdentifierReference -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, target.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
}
else -> {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda P8ESTACK_LO,x")
popAndWriteArrayvalueWithUnscaledIndexA(target.datatype, target.asmVarname)
}
if (target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
else {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, target.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${target.asmVarname},y")
}
}
TargetStorageKind.REGISTER -> {
@ -556,10 +593,69 @@ 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")
private fun assignVariableByteIntoWord(wordtarget: AsmAssignTarget, bytevar: IdentifierReference) {
val sourceName = asmgen.asmVariableName(bytevar)
when (wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda $sourceName
sta ${wordtarget.asmVarname}
ora #$7f
bmi +
lda #0
+ sta ${wordtarget.asmVarname}+1
""")
}
TargetStorageKind.ARRAY -> {
// TODO optimize slow stack evaluation for this case, see assignVariableUByteIntoWord
if(this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for sign-extend byte typecast at ${bytevar.position}")
asmgen.translateExpression(wordtarget.origAssign.source.expression!!)
assignStackValue(wordtarget)
}
TargetStorageKind.REGISTER -> {
when(wordtarget.register!!) {
RegisterOrPair.AX -> asmgen.out("""
lda $sourceName
pha
ora #$7f
bmi +
ldx #0
+ tax
pla""")
RegisterOrPair.AY -> asmgen.out("""
lda $sourceName
pha
ora #$7f
bmi +
ldy #0
+ tay
pla""")
RegisterOrPair.XY -> asmgen.out("""
lda $sourceName
tax
ora #$7f
bmi +
ldy #0
+ tay""")
else -> throw AssemblyError("only reg pairs are words")
}
}
TargetStorageKind.STACK -> {
asmgen.out("""
lda $sourceName
sta P8ESTACK_LO,x
ora #$7f
bmi +
lda #0
+ sta P8ESTACK_HI,x
dex""")
}
else -> throw AssemblyError("target type isn't word")
}
}
private fun assignVariableUByteIntoWord(wordtarget: AsmAssignTarget, bytevar: IdentifierReference) {
val sourceName = asmgen.asmVariableName(bytevar)
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
@ -571,22 +667,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
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)
}
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx | lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1")
}
else {
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array!!, wordtarget.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname},y | lda #0 | iny | sta ${wordtarget.asmVarname},y")
}
}
TargetStorageKind.REGISTER -> {
@ -599,13 +686,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.STACK -> {
asmgen.out("""
lda #$sourceName
lda $sourceName
sta P8ESTACK_LO,x
lda #0
sta P8ESTACK_HI,x
dex""")
}
else -> throw AssemblyError("other types aren't word")
else -> throw AssemblyError("target type isn't word")
}
}
@ -619,40 +706,21 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
storeRegisterInMemoryAddress(register, target.memory!!)
}
TargetStorageKind.ARRAY -> {
val index = target.array!!.arrayspec.index
when (index) {
is NumericLiteralValue -> {
val memindex = index.number.toInt()
when {
target.constArrayIndexValue!=null -> {
when (register) {
CpuRegister.A -> asmgen.out(" sta ${target.asmVarname}+$memindex")
CpuRegister.X -> asmgen.out(" stx ${target.asmVarname}+$memindex")
CpuRegister.Y -> asmgen.out(" sty ${target.asmVarname}+$memindex")
CpuRegister.A -> asmgen.out(" sta ${target.asmVarname}+${target.constArrayIndexValue}")
CpuRegister.X -> asmgen.out(" stx ${target.asmVarname}+${target.constArrayIndexValue}")
CpuRegister.Y -> asmgen.out(" sty ${target.asmVarname}+${target.constArrayIndexValue}")
}
}
is IdentifierReference -> {
else -> {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
asmgen.out(" ldy ${asmgen.asmVariableName(index)} | sta ${target.asmVarname},y")
}
else -> {
asmgen.saveRegister(register)
asmgen.translateExpression(index)
asmgen.restoreRegister(register)
when (register) {
CpuRegister.A -> asmgen.out(" sta P8ZP_SCRATCH_B1")
CpuRegister.X -> asmgen.out(" stx P8ZP_SCRATCH_B1")
CpuRegister.Y -> asmgen.out(" sty P8ZP_SCRATCH_B1")
}
asmgen.out("""
inx
lda P8ESTACK_LO,x
tay
lda P8ZP_SCRATCH_B1
sta ${target.asmVarname},y
""")
asmgen.out(" ldy ${asmgen.asmVariableName(target.array!!.indexer.indexVar!!)} | sta ${target.asmVarname},y")
}
}
}
@ -694,6 +762,54 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignRegisterpairWord(target: AsmAssignTarget, regs: RegisterOrPair) {
require(target.datatype in WordDatatypes)
when(target.kind) {
TargetStorageKind.VARIABLE -> {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname} | stx ${target.asmVarname}+1")
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname} | sty ${target.asmVarname}+1")
RegisterOrPair.XY -> asmgen.out(" stx ${target.asmVarname} | sty ${target.asmVarname}+1")
else -> throw AssemblyError("expected reg pair")
}
}
TargetStorageKind.ARRAY -> {
TODO("store register pair $regs into word-array ${target.array}")
}
TargetStorageKind.REGISTER -> {
when(regs) {
RegisterOrPair.AX -> when(target.register!!) {
RegisterOrPair.AY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG") }
RegisterOrPair.AX -> { }
RegisterOrPair.XY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG | tax") }
else -> throw AssemblyError("expected reg pair")
}
RegisterOrPair.AY -> when(target.register!!) {
RegisterOrPair.AY -> { }
RegisterOrPair.AX -> { asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
RegisterOrPair.XY -> { asmgen.out(" tax") }
else -> throw AssemblyError("expected reg pair")
}
RegisterOrPair.XY -> when(target.register!!) {
RegisterOrPair.AY -> { asmgen.out(" txa") }
RegisterOrPair.AX -> { asmgen.out(" txa | sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
RegisterOrPair.XY -> { }
else -> throw AssemblyError("expected reg pair")
}
else -> throw AssemblyError("expected reg pair")
}
}
TargetStorageKind.STACK -> {
when(regs) {
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex")
RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here")
else -> throw AssemblyError("expected reg pair")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't store word into memory byte")
}
}
private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -717,19 +833,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}")
}
TargetStorageKind.ARRAY -> {
// TODO optimize this, but the situation doesn't occur very often
// if(target.constArrayIndexValue!=null) {
// TODO("const index ${target.constArrayIndexValue}")
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
// TODO("array[var] ${target.constArrayIndexValue}")
// }
val index = target.array!!.arrayspec.index
asmgen.translateExpression(index)
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y)
asmgen.out("""
inx
lda P8ESTACK_LO,x
asl a
tay
lda #<${word.toHex()}
sta ${target.asmVarname},y
lda #>${word.toHex()}
@ -764,25 +869,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
storeByteViaRegisterAInMemoryAddress("#${byte.toHex()}", target.memory!!)
}
TargetStorageKind.ARRAY -> {
val index = target.array!!.arrayspec.index
when {
target.constArrayIndexValue!=null -> {
val indexValue = target.constArrayIndexValue!!
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname}+$indexValue")
}
index is IdentifierReference -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array, DataType.UBYTE, CpuRegister.Y)
asmgen.out(" lda #<${byte.toHex()} | sta ${target.asmVarname},y")
}
else -> {
asmgen.translateExpression(index)
asmgen.out("""
inx
ldy P8ESTACK_LO,x
lda #${byte.toHex()}
sta ${target.asmVarname},y
""")
}
if (target.constArrayIndexValue!=null) {
val indexValue = target.constArrayIndexValue!!
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname}+$indexValue")
}
else {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UBYTE, CpuRegister.Y)
asmgen.out(" lda #<${byte.toHex()} | sta ${target.asmVarname},y")
}
}
TargetStorageKind.REGISTER -> when(target.register!!) {
@ -826,15 +919,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
// TODO optimize this, but the situation doesn't occur very often
// if(target.constArrayIndexValue!=null) {
// TODO("const index ${target.constArrayIndexValue}")
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
// TODO("array[var] ${target.constArrayIndexValue}")
// }
val index = target.array!!.arrayspec.index
if (index is NumericLiteralValue) {
val indexValue = index.number.toInt() * DataType.FLOAT.memorySize()
if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * DataType.FLOAT.memorySize()
asmgen.out("""
lda #0
sta ${target.asmVarname}+$indexValue
@ -844,13 +930,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta ${target.asmVarname}+$indexValue+4
""")
} else {
asmgen.translateExpression(index)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
asmgen.out("""
lda #<${target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${target.asmVarname}
sta P8ZP_SCRATCH_W1+1
jsr c64flt.set_0_array_float
lda $asmvarname
jsr floats.set_0_array_float
""")
}
}
@ -858,7 +945,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 {
@ -880,16 +967,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
// TODO optimize this, but the situation doesn't occur very often
// if(target.constArrayIndexValue!=null) {
// TODO("const index ${target.constArrayIndexValue}")
// } else if(target.array!!.arrayspec.index is IdentifierReference) {
// TODO("array[var] ${target.constArrayIndexValue}")
// }
val index = target.array!!.arrayspec.index
val arrayVarName = target.asmVarname
if (index is NumericLiteralValue) {
val indexValue = index.number.toInt() * DataType.FLOAT.memorySize()
if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * DataType.FLOAT.memorySize()
asmgen.out("""
lda $constFloat
sta $arrayVarName+$indexValue
@ -903,7 +983,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta $arrayVarName+$indexValue+4
""")
} else {
asmgen.translateExpression(index)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
asmgen.out("""
lda #<${constFloat}
sta P8ZP_SCRATCH_W1
@ -913,7 +993,8 @@ 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
lda $asmvarname
jsr floats.set_array_float
""")
}
}
@ -921,7 +1002,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")
}
}
}
@ -1057,6 +1138,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.storeByteIntoPointer(addressExpr, ldaInstructionArg)
}
else -> {
asmgen.out(" lda $ldaInstructionArg | pha")
asmgen.translateExpression(addressExpr)
asmgen.out("""
inx
@ -1064,8 +1146,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta P8ZP_SCRATCH_W2
lda P8ESTACK_HI,x
sta P8ZP_SCRATCH_W2+1
lda $ldaInstructionArg
ldy #0
pla
sta (P8ZP_SCRATCH_W2),y""")
}
}
@ -1089,9 +1171,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.storeByteIntoPointer(addressExpr, null)
}
else -> {
asmgen.saveRegister(register)
asmgen.saveRegister(register, false, memoryAddress.definingSubroutine())
asmgen.translateExpression(addressExpr)
asmgen.restoreRegister(CpuRegister.A)
asmgen.restoreRegister(CpuRegister.A, false)
asmgen.out("""
inx
ldy P8ESTACK_LO,x
@ -1103,24 +1185,4 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
}
private fun popAndWriteArrayvalueWithUnscaledIndexA(elementDt: DataType, asmArrayvarname: String) {
when (elementDt) {
in ByteDatatypes ->
asmgen.out(" tay | inx | lda P8ESTACK_LO,x | sta $asmArrayvarname,y")
in WordDatatypes ->
asmgen.out(" asl a | tay | inx | lda P8ESTACK_LO,x | sta $asmArrayvarname,y | lda P8ESTACK_HI,x | sta $asmArrayvarname+1,y")
DataType.FLOAT ->
// scaling * 5 is done in the subroutine that's called
asmgen.out("""
sta P8ESTACK_LO,x
dex
lda #<$asmArrayvarname
ldy #>$asmArrayvarname
jsr c64flt.pop_float_to_indexed_var
""")
else ->
throw AssemblyError("weird array type")
}
}
}

View File

@ -32,7 +32,7 @@ internal object CX16MachineDefinition: IMachineDefinition {
override fun getFloatRomConst(number: Double): String? = null // Cx16 has no pulblic ROM float locations
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "cx16lib")
importer.importLibraryModule(program, "syslib")
}
override fun launchEmulator(programName: String) {
@ -52,6 +52,8 @@ internal object CX16MachineDefinition: IMachineDefinition {
}
}
override fun isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
}
@ -78,13 +80,6 @@ internal object CX16MachineDefinition: IMachineDefinition {
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
else -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")

View File

@ -101,7 +101,8 @@ val BuiltinFunctions = mapOf(
"rightstr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null)
FParam("length", setOf(DataType.UBYTE))), null),
"strcmp" to FSignature(false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
)
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
@ -285,9 +286,9 @@ private fun builtinStrlen(args: List<Expression>, position: Position, program: P
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
if(vardecl!=null) {
if(vardecl.datatype!=DataType.STR)
if(vardecl.datatype!=DataType.STR && vardecl.datatype!=DataType.UWORD)
throw SyntaxError("strlen must have string argument", position)
if(vardecl.autogeneratedDontRemove) {
if(vardecl.autogeneratedDontRemove && vardecl.value!=null) {
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
}
}

View File

@ -0,0 +1,80 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
internal class BinExprSplitter(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
// 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, assignment.definingScope()),
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) =
if (target.identifier!=null || target.memoryAddress!=null || target.arrayindexed!=null)
target.isInRegularRAM(namespace)
else
false
}

View File

@ -1,11 +1,10 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ErrorReporter
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.IAstVisitor
@ -25,7 +24,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val calls = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
@ -79,8 +78,10 @@ class CallGraph(private val program: Program) : IAstVisitor {
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val scope = directive.definingScope()
scanAssemblyCode(asm, directive, scope)
val scope = directive.definingSubroutine()
if(scope!=null) {
scanAssemblyCode(asm, directive, scope)
}
}
super.visit(directive)
@ -167,12 +168,12 @@ class CallGraph(private val program: Program) : IAstVisitor {
override fun visit(inlineAssembly: InlineAssembly) {
// parse inline asm for subroutine calls (jmp, jsr)
val scope = inlineAssembly.definingScope()
val scope = inlineAssembly.definingSubroutine()
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
super.visit(inlineAssembly)
}
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
asm.lines().forEach { line ->
val matches = asmJumpRx.matchEntire(line)
if (matches != null) {
@ -180,13 +181,15 @@ class CallGraph(private val program: Program) : IAstVisitor {
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
val node = program.namespace.lookup(jumptarget.split('.'), context)
if (node is Subroutine) {
calls[scope] = calls.getValue(scope).plus(node)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
} else if (jumptarget.contains('.')) {
// maybe only the first part already refers to a subroutine
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
if (node2 is Subroutine) {
calls[scope] = calls.getValue(scope).plus(node2)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node2)
calledBy[node2] = calledBy.getValue(node2).plus(context)
}
}
@ -199,7 +202,8 @@ class CallGraph(private val program: Program) : IAstVisitor {
if (target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
calls[scope] = calls.getValue(scope).plus(node)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
}
}
@ -208,4 +212,55 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
}
}
fun checkRecursiveCalls(errors: ErrorReporter) {
val cycles = recursionCycles()
if(cycles.any()) {
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)
val printed = mutableSetOf<Subroutine>()
for(chain in cycles) {
if(chain[0] !in printed) {
val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" }
errors.warn("Cycle in (a subroutine call in) $chainStr", Position.DUMMY)
printed.add(chain[0])
}
}
}
}
private fun recursionCycles(): List<List<Subroutine>> {
val chains = mutableListOf<MutableList<Subroutine>>()
for(caller in calls.keys) {
val visited = calls.keys.associateWith { false }.toMutableMap()
val recStack = calls.keys.associateWith { false }.toMutableMap()
val chain = mutableListOf<Subroutine>()
if(hasCycle(caller, visited, recStack, chain))
chains.add(chain)
}
return chains
}
private fun hasCycle(sub: Subroutine, visited: MutableMap<Subroutine, Boolean>, recStack: MutableMap<Subroutine, Boolean>, chain: MutableList<Subroutine>): Boolean {
// mark current node as visited and add to recursion stack
if(recStack[sub]==true)
return true
if(visited[sub]==true)
return false
// mark visited and add to recursion stack
visited[sub] = true
recStack[sub] = true
// recurse for all neighbours
for(called in calls.getValue(sub)) {
if(hasCycle(called, visited, recStack, chain)) {
chain.add(called)
return true
}
}
// pop from recursion stack
recStack[sub] = false
return false
}
}

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.instance.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", litval.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
}
else -> {
// nothing to do for this type
// this includes strings and structs
}
}
}
val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declValue.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
}
return noModifications
}
}
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
@ -216,9 +44,24 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
}
"~" -> when (subexpr.type) {
in IntegerDatatypes -> {
DataType.BYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalInteger(subexpr.number.toInt().inv(), subexpr.position),
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
parent))
}
DataType.UBYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UBYTE, subexpr.number.toInt().inv() and 255, subexpr.position),
parent))
}
DataType.WORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
parent))
}
DataType.UWORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UWORD, subexpr.number.toInt().inv() and 65535, subexpr.position),
parent))
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)

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) : 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?.indexVar?.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){
val arraysize = decl.arraysize
if(arraysize==null) {
// for arrays that have no size specifier 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(arraysize.constIndex()==null) {
// see if we can calculate the size from other fields
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
if(cval!=null) {
arraysize.indexVar = null
arraysize.origExpression = null
arraysize.indexNum = cval
}
}
}
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

@ -175,28 +175,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) {
@ -373,6 +351,13 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
// no need to check for left val constant (because of associativity)
val rnum = rightVal?.number?.toDouble()
if(rnum!=null && rnum<0.0) {
expr.operator = "-"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
return expr
}
return null
}
@ -387,12 +372,16 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when (rightConst.number.toDouble()) {
0.0 -> {
// left
return expr.left
}
val rnum = rightVal.number.toDouble()
if (rnum == 0.0) {
// left
return expr.left
}
if(rnum<0.0) {
expr.operator = "+"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position)
return expr
}
}
if (leftVal != null) {
@ -405,6 +394,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
}
}
return null
}
@ -627,6 +617,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (amount >= 16) {
return NumericLiteralValue(targetDt, 0, expr.position)
} else if (amount >= 8) {
// TODO is this correct???
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
if (amount == 8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
@ -666,8 +657,9 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
return NumericLiteralValue.optimalInteger(0, expr.position)
} else if (amount >= 8) {
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8)
return msb
if (amount == 8) {
return TypecastExpression(msb, DataType.UWORD, true, expr.position)
}
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
}
}
@ -675,14 +667,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (amount > 16) {
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
return null
} else if (amount >= 8) {
val msbAsByte = TypecastExpression(
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
DataType.BYTE,
true, expr.position)
if (amount == 8)
return msbAsByte
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
}
}
else -> {

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)
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()
}
}
}
}
@ -42,3 +53,9 @@ internal fun Program.simplifyExpressions() : Int {
opti.visit(this)
return opti.applyModifications()
}
internal fun Program.splitBinaryExpressions() : Int {
val opti = BinExprSplitter(this)
opti.visit(this)
return opti.applyModifications()
}

View File

@ -25,12 +25,12 @@ internal class StatementOptimizer(private val program: Program,
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent))
return listOf(IAstModification.Remove(block, parent as INameScope))
}
if (block !in callgraph.usedSymbols) {
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent))
return listOf(IAstModification.Remove(block, parent as INameScope))
}
}
return noModifications
@ -42,38 +42,28 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine.containsNoCodeNorVars()) {
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = callgraph.calledBy.getValue(subroutine).map {
IAstModification.Remove(it, it.parent)
IAstModification.Remove(it, it.definingScope())
}.toMutableList()
removals += IAstModification.Remove(subroutine, parent)
removals += IAstModification.Remove(subroutine, subroutine.definingScope())
return removals
}
}
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))
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
}
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) {
if(decl.type == VarDeclType.VAR)
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent))
return listOf(IAstModification.Remove(decl, decl.definingScope()))
}
return noModifications
@ -84,7 +74,7 @@ internal class StatementOptimizer(private val program: Program,
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent))
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
}
}
@ -137,7 +127,7 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return)
return listOf(IAstModification.Remove(functionCallStatement, parent))
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
}
return noModifications
@ -160,7 +150,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
return listOf(IAstModification.Remove(ifStatement, parent))
return listOf(IAstModification.Remove(ifStatement, ifStatement.definingScope()))
// empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
@ -193,12 +183,12 @@ internal class StatementOptimizer(private val program: Program,
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars()) {
errors.warn("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, parent))
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
// remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, parent))
return listOf(IAstModification.Remove(forLoop, forLoop.definingScope()))
}
}
@ -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 {
@ -275,7 +265,7 @@ internal class StatementOptimizer(private val program: Program,
} else {
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
listOf(IAstModification.Remove(whileLoop, parent))
listOf(IAstModification.Remove(whileLoop, whileLoop.definingScope()))
}
}
return noModifications
@ -286,12 +276,12 @@ internal class StatementOptimizer(private val program: Program,
if(iter!=null) {
if(repeatLoop.body.containsNoCodeNorVars()) {
errors.warn("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
}
val iterations = iter.constValue(program)?.number?.toInt()
if (iterations == 0) {
errors.warn("iterations is always 0, removed loop", iter.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
return listOf(IAstModification.Remove(repeatLoop, repeatLoop.definingScope()))
}
if (iterations == 1) {
errors.warn("iterations is always 1", iter.position)
@ -318,7 +308,7 @@ internal class StatementOptimizer(private val program: Program,
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, parent))
return listOf(IAstModification.Remove(jump, jump.definingScope()))
return noModifications
}
@ -351,7 +341,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, parent))
IAstModification.InsertAfter(assignment, addConstant, assignment.definingScope()))
} else if (op2 == "-") {
// A = A +/- B - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
@ -362,7 +352,7 @@ internal class StatementOptimizer(private val program: Program,
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, subConstant, parent))
IAstModification.InsertAfter(assignment, subConstant, assignment.definingScope()))
}
}
}
@ -384,7 +374,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) {
// remove assignment to self
return listOf(IAstModification.Remove(assignment, parent))
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
}
val targetIDt = assignment.target.inferType(program, assignment)
@ -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) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..4.0) {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
} 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) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if (vardeclDt != VarDeclType.MEMORY && cv in 1.0..4.0) {
if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
} 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, assignment.definingScope()))
"/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
"<<" -> {
if (cv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
}
">>" -> {
if (cv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
}
}
}
}
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)
@ -19,20 +20,20 @@ internal class UnusedCodeRemover(private val errors: ErrorReporter): AstWalker()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
removals.add(IAstModification.Remove(sub, sub.definingScope()))
}
}
}
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
removals.add(IAstModification.Remove(block, block.definingScope()))
}
// remove modules that are not imported, or are empty (unless it's a library modules)
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removals.add(IAstModification.Remove(it, it.parent))
removals.add(IAstModification.Remove(it, it.definingScope()))
}
return removals
@ -66,4 +67,37 @@ 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)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
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, "c64"))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, errors)
@ -142,37 +145,37 @@ class TestC64Zeropage {
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, "c64"))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, "c64"))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, "c64"))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
zp3.allocate("", DataType.FLOAT, null, errors)
}
@Test
fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, "c64"))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, "c64"))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, "c64"))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, "c64"))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, "c64"))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, "c64"))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, "c64"))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false))
}
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, "c64"))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false))
}
}
@Test
fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, "c64"))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false))
println(zp.free)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
@ -182,19 +185,19 @@ class TestC64Zeropage {
@Test
fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, "c64"))
assertEquals(16, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, "c64"))
assertEquals(91, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, "c64"))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(18, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
assertEquals(89, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, "c64"))
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertEquals(238, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, "c64"))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertEquals(238, zp1.available())
assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free)
@ -203,7 +206,7 @@ class TestC64Zeropage {
assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, "c64"))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false))
assertEquals(139, zp2.available())
assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free)
@ -216,8 +219,8 @@ class TestC64Zeropage {
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, "c64"))
assertEquals(16, zp.available())
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(18, zp.available())
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free
@ -239,7 +242,7 @@ class TestC64Zeropage {
@Test
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, "c64"))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3)
@ -269,17 +272,19 @@ class TestC64Zeropage {
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, "c64"))
assertEquals(16, zp.available())
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(18, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x9b, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x9e, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb0, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xbe, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x92, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x96, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0, zp.available())
}
@ -379,3 +384,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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -41,7 +41,7 @@ of that build task, you can start the compiler with:
(You should probably make an alias...)
.. note::
.. hint::
Development and testing is done on Linux, but the compiler should run on most
operating systems. If you do have trouble building or running
the compiler on another operating system, please let me know!
@ -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

@ -38,21 +38,22 @@ This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/license
:alt: Fully playable tetris clone
Code examples
-------------
Code example
------------
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64textio
%import textio
%zeropage basicsafe
main {
ubyte[256] sieve
ubyte candidate_prime = 2
ubyte candidate_prime = 2 ; is increased in the loop
sub start() {
memset(sieve, 256, false) ; clear the sieve
; clear the sieve, to reset starting situation on subsequent runs
memset(sieve, 256, false)
; calculate primes
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0
repeat {
@ -63,18 +64,19 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
txt.print(", ")
amount++
}
c64.CHROUT('\n')
txt.chrout('\n')
txt.print("number of primes (expected 54): ")
txt.print_ub(amount)
c64.CHROUT('\n')
txt.chrout('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0 ; we wrapped; no more primes available
return 0 ; we wrapped; no more primes available in the sieve
}
; found next one, mark the multiples and return it.
sieve[candidate_prime] = true
uword multiple = candidate_prime
@ -88,51 +90,19 @@ 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
:align: center
:alt: result when run on C-64
when the exact same program is compiled for the Commander X16 target, and run on the emulator, you get this:
The following programs shows a use of the high level ``struct`` type::
.. image:: _static/primes_cx16.png
:align: center
:alt: result when run on CX16 emulator
%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 +132,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:
@ -200,6 +171,7 @@ If you're targeting the CommanderX16, there's the `x16emu <https://github.com/co
building.rst
programming.rst
syntaxreference.rst
libraries.rst
todo.rst

94
docs/source/libraries.rst Normal file
View File

@ -0,0 +1,94 @@
************************
Compiler library modules
************************
The compiler provides several "built-in" library modules with useful subroutine and variables.
Some of these may be specific for a certain compilation target, or work slightly different,
but some effort is put into making them available across compilation targets.
This means that as long as your program is only using the subroutines from these
libraries and not using hardware- and/or system dependent code, and isn't hardcoding certain
assumptions like the screen size, the exact same source program can
be compiled for multiple different target platforms. Many of the example programs that come
with Prog8 are written like this.
You can ``%import`` and use these modules explicitly, but the compiler may also import one or more
of these library modules automatically as required.
.. caution::
The resulting compiled binary program *only works on the target machine it was compiled for*.
You must recompile the program for every target you want to run it on.
syslib
------
The "system library" for your target machine. It contains many system-specific definitions such
as ROM/kernal subroutine definitions, memory location constants, and utility subroutines.
Many of these definitions overlap for the C64 and Commander X16 targets so it is still possible
to write programs that work on both targets without modifications.
conv
----
Routines to convert strings to numbers or vice versa.
- numbers to strings, in various formats (binary, hex, decimal)
- strings in decimal, hex and binary format into numbers
textio (txt.*)
--------------
This will probably be the most used library module. It contains a whole lot of routines
dealing with text-based input and output (to the screen). Such as
- printing strings and numbers
- reading text input from the user via the keyboard
- filling or clearing the screen and colors
- scrolling the text on the screen
- placing individual characters on the screen
diskio
------
Provides several routines that deal with disk drive I/O, such as:
- show directory
- display disk drive status
- load and save data from and to the disk
- delete and rename files on the disk
floats
------
Provides definitions for the ROM/kernel subroutines and utility routines dealing with floating
point variables. This includes ``print_f``, the routine used to print floating point numbers.
graphics
--------
High-res monochrome bitmap graphics routines:
- clearing the screen
- drawing lines
- drawing circles and discs (filled circles)
- plotting individual pixels
math
----
Low level math routines. You should not normally have to bother with this directly.
The compiler needs it to implement most of the math operations in your programs.
cx16logo
--------
A 'fun' module that contains the Commander X16 logo and that allows you
to print it anywhere on the screen.
prog8_lib
---------
Low level language support. You should not normally have to bother with this directly.
The compiler needs it for verious built-in system routines.

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
@ -257,6 +260,12 @@ Note that the various keywords for the data type and variable type (``byte``, ``
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance.
It's possible to assign a new array to another array, this will overwrite all elements in the original
array with those in the value array. The number and types of elements have to match.
For large arrays this is a slow operation because every element is copied over. It should probably be avoided.
**Arrays at a specific memory location:**
Using the memory-mapped syntax it is possible to define an array to be located at a specific memory location.
For instance to reference the first 5 rows of the Commodore 64's screen matrix as an array, you can define::
@ -284,7 +293,7 @@ This @-prefix can also be used for character byte values.
You can concatenate two string literals using '+' (not very useful though) or repeat
a string literal a given number of times using '*'. You can also assign a new string
value to another string. No bounds check is done so be sure the destination string is
large enough to contain the new value::
large enough to contain the new value (it is overwritten in memory)::
str string1 = "first part" + "second part"
str string2 = "hello!" * 10
@ -293,6 +302,18 @@ large enough to contain the new value::
string1 = "new value"
There are several 'escape sequences' to help you put special characters into strings, such
as newlines, quote characters themselves, and so on. The ones used most often are
``\\``, ``\"``, ``\n``, ``\r``. For a detailed description of all of them and what they mean,
read the syntax reference on strings.
.. hint::
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 +348,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
@ -352,13 +373,6 @@ address you specified, and setting the varible will directly modify that memory
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
.. note::
Directly accessing random memory locations is not yet supported without the
intermediate step of declaring a memory-mapped variable for the memory location.
The advantages of this however, is that it's clearer what the memory location
stands for, and the compiler also knows the data type.
Converting types into other types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -578,25 +592,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)
@ -637,23 +650,20 @@ There's a set of predefined functions in the language. These are fixed and can't
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
sin(x)
Sine. (floating point version)
Math
^^^^
abs(x)
Absolute value.
atan(x)
Arctangent.
ceil(x)
Rounds the floating point up to an integer towards positive infinity.
cos(x)
Cosine. (floating point version)
sin8u(x)
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
sin8(x)
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
sin16u(x)
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
sin16(x)
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
Cosine. (floating point version)
cos8u(x)
Fast 8-bit ubyte cosine of angle 0..255, result is in range 0..255
@ -667,14 +677,11 @@ cos16u(x)
cos16(x)
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
abs(x)
Absolute value.
deg(x)
Radians to degrees.
tan(x)
Tangent.
atan(x)
Arctangent.
floor (x)
Rounds the floating point down to an integer towards minus infinity.
ln(x)
Natural logarithm (base e).
@ -682,45 +689,48 @@ ln(x)
log2(x)
Base 2 logarithm.
rad(x)
Degrees to radians.
round(x)
Rounds the floating point to the closest integer.
sin(x)
Sine. (floating point version)
sgn(x)
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
sin8u(x)
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
sin8(x)
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
sin16u(x)
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
sin16(x)
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
sqrt16(w)
16 bit unsigned integer Square root. Result is unsigned byte.
sqrt(x)
Floating point Square root.
round(x)
Rounds the floating point to the closest integer.
tan(x)
Tangent.
floor (x)
Rounds the floating point down to an integer towards minus infinity.
ceil(x)
Rounds the floating point up to an integer towards positive infinity.
Array operations
^^^^^^^^^^^^^^^^
rad(x)
Degrees to radians.
any(x)
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
deg(x)
Radians to degrees.
max(x)
Maximum of the values in the array value x
min(x)
Minimum of the values in the array value x
sum(x)
Sum of the values in the array value x
sort(array)
Sort the array in ascending order (in-place)
Note: sorting a floating-point array is not supported right now, as a general sorting routine for this will
be extremely slow. Either build one yourself or find another solution that doesn't require sorting
floating point values.
reverse(array)
Reverse the values in the array (in-place). Supports all data types including floats.
Can be used after sort() to sort an array in descending order.
all(x)
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
len(x)
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
@ -729,15 +739,80 @@ len(x)
length of the string during execution, the value of len(string) may no longer be correct!
(use strlen function if you want to dynamically determine the length)
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
max(x)
Maximum of the values in the array value x
min(x)
Minimum of the values in the array value x
reverse(array)
Reverse the values in the array (in-place).
Can be used after sort() to sort an array in descending order.
sum(x)
Sum of the values in the array value x
sort(array)
Sort the array in ascending order (in-place)
Supported are arrays of bytes or word values.
Sorting a floating-point array is not supported right now, as a general sorting routine for this will
be extremely slow. Either build one yourself or find another solution that doesn't require sorting.
Finally, note that sorting an array with strings in it will not do what you might think;
it considers the array as just an array of integer words and sorts the string *pointers* accordingly.
Sorting strings alphabetically has to be programmed yourself if you need it.
Strings and memory blocks
^^^^^^^^^^^^^^^^^^^^^^^^^
memcopy(from, to, numbytes)
Efficiently copy a number of bytes (1 - 256) from a memory location to another.
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not.
The most efficient will always be to write a specialized copy routine in assembly yourself!
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 ``txt`` block (part of the ``textio`` module)
memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
leftstr(source, target, length)
Copies the left side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rightstr(source, target, length)
Copies the right side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
strlen(str)
Number of bytes in the string. This value is determined during runtime and counts upto
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
Don't confuse this with ``len`` and ``sizeof``
strcmp(string1, string2)
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
Note that you can also directly compare strings and string values with eachother
using ``==``, ``<`` etcetera (it will use strcmp for you under water automatically).
substr(source, target, start, length)
Copies a segment from the source string, starting at the given index,
and of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
Miscellaneous
^^^^^^^^^^^^^
exit(returncode)
Immediately stops the program and exits it, with the returncode in the A register.
Note: custom interrupt handlers remain active unless manually cleared first!
lsb(x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
@ -745,19 +820,10 @@ lsb(x)
msb(x)
Get the most significant byte of the word x.
sgn(x)
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
mkword(msb, lsb)
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
So mkword($80, $22) results in $8022.
any(x)
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
all(x)
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
rnd()
returns a pseudo-random byte from 0..255
@ -791,51 +857,6 @@ ror2(x)
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
memcopy(from, to, numbytes)
Efficiently copy a number of bytes (1 - 256) from a memory location to another.
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not.
The most efficient will always be to write a specialized copy routine in assembly yourself!
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)
memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
leftstr(source, target, length)
Copies the left side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rightstr(source, target, length)
Copies the right side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
substr(source, target, start, length)
Copies a segment from the source string, starting at the given index,
and of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way.
set_carry() / clear_carry()
Set (or clear) the CPU status register Carry flag. No result value.
(translated into ``SEC`` or ``CLC`` cpu instruction)
set_irqd() / clear_irqd()
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction)
rsave()
Saves the CPU registers and the status flags.
You can now more or less 'safely' use the registers directly, until you
@ -850,10 +871,22 @@ rrestore()
read_flags()
Returns the current value of the CPU status register.
exit(returncode)
Immediately stops the program and exits it, with the returncode in the A register.
Note: custom interrupt handlers remain active unless manually cleared first!
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
set_carry() / clear_carry()
Set (or clear) the CPU status register Carry flag. No result value.
(translated into ``SEC`` or ``CLC`` cpu instruction)
set_irqd() / clear_irqd()
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction)
swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way.
Library routines

View File

@ -36,9 +36,8 @@ Directives
.. data:: %target <target>
Level: module.
Global setting, selects a compilation target from within the source file.
The default compilation target is "c64" which targets the Commodore-64 machine.
You can also omit this and use the ``-target`` command line option.
Global setting, specifies that this module can only work for the given compiler target.
If compiled with a different target, compilation is aborted with an error message.
.. data:: %output <type>
@ -68,7 +67,8 @@ Directives
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
including default IRQs during normal system operation.
When the program exits, a system reset is performed (because BASIC will be in a corrupt state).
It's not possible to return cleanly to BASIC when the program exits. The only choice is
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
are required to perform floating point operations (from the BASIC kernel). No clean exit is possible.
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
@ -78,10 +78,11 @@ Directives
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
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 includes many floating point operations and several utility routines that do I/O, such as ``print``.
This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code).
It's not possible to return cleanly to BASIC when the program exits. The only choice is
to perform a system reset. (A ``system_reset`` subroutine is available in the syslib to help you do this)
- style ``dontuse`` -- don't use *any* location in the zeropage.
Also read :ref:`zeropage`.
@ -118,33 +119,38 @@ Directives
Level: module, block.
Sets special compiler options.
For a module option, only the ``enable_floats`` option is recognised, which will tell the compiler
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
Otherwise, floating point support is not enabled.
When used in a block with the ``force_output`` option, it will force the block to be outputted
in the final program. Can be useful to make sure some
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
- For a module option, there is ``enable_floats``, which will tell the compiler
to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
Otherwise, floating point support is not enabled.
- There's also ``no_sysinit`` which cause the resulting program to *not* include
the system re-initialization logic of clearing the screen, resetting I/O config etc. You'll have to
take care of that yourself. The program will just start running from whatever state the machine is in when the
program was launched.
- When used in a block with the ``force_output`` option, it will force the block to be outputted
in the final program. Can be useful to make sure some
data is generated that would otherwise be discarded because it's not referenced (such as sprite data).
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
Level: block.
This directive can only be used inside a block.
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
The optional offset and length can be used to select a particular piece of the file.
The file is located relative to the current working directory!
Level: block.
This directive can only be used inside a block.
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
The optional offset and length can be used to select a particular piece of the file.
The file is located relative to the current working directory!
.. data:: %asminclude "<filename>", "scopelabel"
Level: block.
This directive can only be used inside a block.
The assembler will include the file as raw assembly source text at this point,
prog8 will not process this at all, with one exception: the labels.
The scopelabel argument will be used as a prefix to access the labels from the included source code,
otherwise you would risk symbol redefinitions or duplications.
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
if the file can't be found there it is searched relative to the current directory.
Level: block.
This directive can only be used inside a block.
The assembler will include the file as raw assembly source text at this point,
prog8 will not process this at all, with one exception: the labels.
The scopelabel argument will be used as a prefix to access the labels from the included source code,
otherwise you would risk symbol redefinitions or duplications.
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
if the file can't be found there it is searched relative to the current directory.
.. data:: %breakpoint
@ -275,6 +281,7 @@ type identifier type storage size example var declara
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
``uword[]`` unsigned word array depends on value ``uword[] myvar = [1, 2, 3, 4]``
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]``
``str`` string (petscii) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte
=============== ======================= ================= =========================================
@ -297,7 +304,8 @@ of something with an operand starting with 1 or 0, you'll have to add a space in
- When an integer value ranges from 256..65535 the compiler sees it as a ``uword``. For -32768..32767 it's a ``word``.
- When a hex number has 3 or 4 digits, for example ``$0004``, it is seen as a ``word`` otherwise as a ``byte``.
- When a binary number has 9 to 16 digits, for example ``%1100110011``, it is seen as a ``word`` otherwise as a ``byte``.
- You can force a byte value into a word value by adding the ``.w`` datatype suffix to the number: ``$2a.w`` is equivalent to ``$002a``.
- If the number fits in a byte but you really require it as a word value, you'll have to explicitly cast it: ``60 as uword``
or you can use the full word hexadecimal notation ``$003c``.
Data type conversion
@ -391,7 +399,25 @@ 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
String
^^^^^^
``"hello"`` is a string translated into the default character encoding (PETSCII)
``@"hello"`` is a string translated into the alternate character encoding (Screencodes/pokes)
There are several escape sequences available to put special characters into your string value:
- ``\\`` - the backslash itself, has to be escaped because it is the escape symbol by itself
- ``\n`` - newline character (move cursor down and to beginning of next line)
- ``\r`` - carriage return character (more or less the same as newline if printing to the screen)
- ``\"`` - quote character (otherwise it would terminate the string)
- ``\'`` - apostrophe character (has to be escaped in character literals, is okay inside a string)
- ``\uHHHH`` - a unicode codepoint \u0000 - \uffff (16-bit hexadecimal)
- ``\xHH`` - 8-bit hex value that will be copied verbatim *without encoding*
Operators
@ -478,6 +504,8 @@ takes no parameters. If the subroutine returns a value, usually you assign it t
If you're not interested in the return value, prefix the function call with the ``void`` keyword.
Otherwise the compiler will warn you about discarding the result of the call.
Multiple return values
^^^^^^^^^^^^^^^^^^^^^^
Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
(referencing a routine in kernel ROM) can return more than one return value.
@ -485,9 +513,16 @@ For example a status in the carry bit and a number in A, or a 16-bit value in A/
It is not possible to process the results of a call to these kind of routines
directly from the language, because only single value assignments are possible.
You can still call the subroutine and not store the results.
But if you want to do something with the values it returns, you'll have to write
a small block of custom inline assembly that does the call and stores the values
appropriately. Don't forget to save/restore the registers if required.
**There is an exception:** if there's just one return value in a register, and one or more others that are returned
as bits in the status register (such as the Carry bit), the compiler allows you to call the subroutine.
It will then store the result value in a variable if required, and *keep the status register untouched
after the call* so you can use a conditional branch statement for that.
Note that this makes no sense inside an expression, so the compiler will still give an error for that.
If there really are multiple return values (other than a combined 16 bit return value in 2 registers),
you'll have to write a small block of custom inline assembly that does the call and stores the values
appropriately. Don't forget to save/restore any registers that are modified.
Subroutine definitions

View File

@ -9,13 +9,17 @@ Prog8 targets the following hardware:
- optional use of memory mapped I/O registers
- optional use of system ROM routines
Currently there are two machines that are supported as compiler target (via the ``-target`` compiler argument):
Currently there are two machines that are supported as compiler target (selectable via the ``-target`` compiler argument):
- 'c64': the well-known Commodore-64, premium support
- 'cx16': the `CommanderX16 <https://www.commanderx16.com/>`_ a project from the 8-Bit Guy. Support for this is still experimental.
This chapter explains the relevant system details of these machines.
.. hint::
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

@ -3,32 +3,26 @@ TODO
====
- get rid of all other TODO's in the code ;-)
- line-circle-gfx examples are now a few hundred bytes larger than before. Why is that, can it be fixed?
- compiler errors and warnings in standard format so the IDE shows them as clickable links; ./test.asm:2578:3: blablabla
- further optimize assignment codegeneration
- auto select correct library to import based on target, instead of having c64- and cx16- prefix variants
- implement @stack for asmsub parameters
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
- aliases for imported symbols for example perhaps '%alias print = c64scr.print' ?
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
- use VIC banking to move up the graphics bitmap memory location. Don't move it under the ROM though as that would require IRQ disabling and memory bank swapping for every bitmap manipulation
- add some primitives/subroutines/examples for using custom char sets, copying the default charset.
- recursive subroutines? via %option recursive, allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
More optimizations
^^^^^^^^^^^^^^^^^^
Add more compiler optimizations to the existing ones.
- more targeted optimizations for assigment asm code, such as the following:
- 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?
- 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. Especially for built-in functions!
- 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,5 +1,5 @@
%import c64flt
%import c64textio
%import floats
%import textio
%zeropage basicsafe
main {
@ -93,11 +93,11 @@ main {
txt.print("err! ")
txt.print("float ")
c64flt.print_f(a1)
floats.print_f(a1)
txt.print(" / ")
c64flt.print_f(a2)
floats.print_f(a2)
txt.print(" = ")
c64flt.print_f(r)
floats.print_f(r)
c64.CHROUT('\n')
}
}

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 {

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

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