Compare commits

...

284 Commits
v2.3 ... v4.4

Author SHA1 Message Date
22031f39b0 update compiled examples 2020-10-02 23:39:20 +02:00
c4673d3a67 v4.4 2020-10-02 23:32:45 +02:00
e83e021541 doc 2020-10-02 23:31:49 +02:00
c1f2ecd413 struct assignment from array value now checks number of elements 2020-10-02 22:48:39 +02:00
46fbe01df9 added codengeration for assigment of array of values to a struct variable (all members at once) 2020-10-02 22:37:52 +02:00
8647a8290e fix code generation for using struct vars in arrays and such 2020-10-02 22:21:18 +02:00
bac51f4b31 fix subtraction error for bytes 2020-10-02 21:30:32 +02:00
582aab180a oops 2020-10-02 02:39:19 +02:00
5fb714fcb2 expression splitter integrated into expression simplifier 2020-10-02 01:54:37 +02:00
3994de77d0 fix expression splitter handling related to code ballooning 2020-10-02 01:49:55 +02:00
24c8d1f1f4 expression splitter for vardecls with binexpr init expression 2020-10-02 00:34:12 +02:00
110f877dcc binexpr expression splitter for assignments 2020-10-02 00:04:21 +02:00
9cd3a9f8e8 fix isSameAs for ArrayIndexed expressions, and by extension, assignment.isAugmentable() 2020-10-01 23:26:43 +02:00
1464050bf5 expression splitter moved to separate optimizer 2020-10-01 02:58:12 +02:00
95e9e1b550 avoid adding unneeded variable initalization assignments. Improved removal of useless double assignments. 2020-10-01 00:39:49 +02:00
bda1c1c1eb reduce slow estack usage by splitting up simple binary expressions 2020-09-30 19:57:16 +02:00
d020a7974a reduce slow estack usage by splitting up simple binary expressions 2020-09-30 17:51:35 +02:00
a51fad3aab parentheses around binary exprs in source output 2020-09-30 16:38:54 +02:00
3cd32778bb don't split expressions referencing the target variable wrongly 2020-09-30 01:11:33 +02:00
8d67056f84 fixed estack corruption caused by c64 print_f 2020-09-29 21:12:16 +02:00
e986973b5e wrong floats 2020-09-29 04:05:45 +02:00
448c934cba optimized neg(x) and abs(x) 2020-09-29 03:58:17 +02:00
96ef7ba55d fixed ast to source for structs 2020-09-29 00:28:11 +02:00
4372de1e7e allow creating arrays of pointers to other arrays. Usefullness is very limited though... 2020-09-29 00:03:47 +02:00
af0fb88adf allow creating string arrays. Fixed array index scaling for word arrays. 2020-09-28 02:23:36 +02:00
066233eee8 todos 2020-09-27 22:05:44 +02:00
b6f85d10b0 reintroduced system reset at program exit if zeropage is clobbered 2020-09-27 22:00:36 +02:00
6f75413c09 some more optimizations in expressions with memreads 2020-09-27 21:43:40 +02:00
d45fe4ce74 fixed invalid eval stack ptr issue 2020-09-27 20:55:34 +02:00
e828c013e6 fix word+/-byte errors if byte was unsigned 2020-09-27 20:23:42 +02:00
988459f744 don't generate a byte storage for every single time a register needs saving 2020-09-27 16:26:02 +02:00
7c701bdf3f corrections 2020-09-27 14:14:45 +02:00
446fc35d5c avoid excessive comparisons for certain comparison expressions against zero 2020-09-27 03:55:59 +02:00
bec9cc7047 asm store/load same optimizer back.... 2020-09-27 02:45:59 +02:00
961380acb6 optimized float ==0 or 1 comparisons 2020-09-27 01:56:08 +02:00
84c0685a60 fix faulty comparison optimization 2020-09-27 01:40:12 +02:00
629222f103 larger 2020-09-26 19:59:57 +02:00
8c448e5bc2 finished optimized comparison asm generation 2020-09-26 19:55:04 +02:00
b5fa6c2d0a library modules imported from embedded resource now contain proper file path (useful for error messages) 2020-09-26 19:30:17 +02:00
680b2df08a just call the asmsub 2020-09-26 19:14:06 +02:00
09bd47f98b > 2020-09-26 19:02:29 +02:00
7f69f9ce4f <= 2020-09-26 18:04:43 +02:00
4179b4e543 all unsigned comparisons 2020-09-26 17:45:35 +02:00
66364554c4 new comparisons testprog 2020-09-26 16:11:47 +02:00
43f2448789 added (u)byte and (u)word '>' 2020-09-26 13:15:03 +02:00
130cee1e70 tweak '<' code 2020-09-26 12:47:40 +02:00
b976360248 fix fallthrough problem with 'when'. Fix too greedy asm optimization that caused conditional jumps to fail sometimes because the condition value wasn't loaded. 2020-09-26 00:22:55 +02:00
225bfc4164 fix 16+8 bit add and sub sign extensions 2020-09-25 22:51:59 +02:00
d7ceda4d82 removed the automatic system reset at program exit, this did't work with the new init code 2020-09-25 22:12:14 +02:00
14d091e60a crashes :( 2020-09-24 23:50:20 +02:00
2809668ef4 new asm code for (u)word and (u)byte < 2020-09-24 23:08:36 +02:00
bafb86e00b new asm code for (n)equals 2020-09-24 22:28:24 +02:00
f5db31b8ff do..until condition can now refer to variables defined in the loop's inner scope. 2020-09-24 19:26:07 +02:00
e1d0dbed0c do..until condition can now refer to variables defined in the loop's inner scope. 2020-09-23 23:24:32 +02:00
1d1fe364d0 added %option no_sysinit to avoid having the system re-initialization code executed at the start of the program 2020-09-23 23:01:47 +02:00
2b9316c4ff reworked program init logic so that it is included as the first thing inside main.start itself, to allow better stand alone asm 2020-09-23 22:29:21 +02:00
c50cbbb526 typo 2020-09-23 18:50:32 +02:00
b93d9ecd7e memtop cx16 2020-09-23 02:34:49 +02:00
96243db88b refresh compiled examples 2020-09-23 00:29:40 +02:00
4daf75a8cc better checks for invalid %output and %launcher values. Added diskdir examples. 2020-09-23 00:22:36 +02:00
8c63d7cf5b diskdir 2020-09-22 23:22:20 +02:00
6f78a32e64 diskdir 2020-09-22 23:12:43 +02:00
af6731c9c8 preparing version 4.3 2020-09-22 21:50:56 +02:00
25cf0d2b94 don't suggest a mult replacement routine to be used, faster ones are likely to require large tables 2020-09-22 21:19:01 +02:00
9389791d91 created own circle and disc subroutines for cx16 because its rom routine is not yet implemented and just does a BRK 2020-09-22 02:52:09 +02:00
aa8191d0a1 introduced graphics module wrapper for cx16 to make even more programs compatible 2020-09-22 02:21:16 +02:00
0d5c78e875 introduced graphics module wrapper for cx16 to make even more programs compatible 2020-09-22 02:12:01 +02:00
e8679ae03b fixed print_f on cx16. Some more examples are now multi-platform. 2020-09-22 01:45:51 +02:00
d1d224b7fc fixed print_f on cx16. Some more examples are now multi-platform. 2020-09-22 01:34:05 +02:00
df995f7bc9 fixed float zp problem on C64, added more zp locations to block list 2020-09-22 01:05:07 +02:00
af39502450 doc 2020-09-22 00:47:02 +02:00
ffa38955d6 improved scroll_down and scroll_up to use VERA dual data ports instead of a copybuffer 2020-09-22 00:34:43 +02:00
8d82fb6d8f added cx16 txt.scroll_right 2020-09-22 00:00:22 +02:00
306770331a added cx16 txt.scroll_left 2020-09-21 23:39:25 +02:00
d3f433c8cf specify VERA data port to use 2020-09-21 23:04:01 +02:00
cf49cbd1f8 more consistent about the system reset routine 2020-09-21 22:35:07 +02:00
8a99e75299 added cx16 txt.scroll_down 2020-09-21 22:06:48 +02:00
2dbf849c82 added cx16 txt.scroll_up 2020-09-21 21:39:36 +02:00
ba3dce0b4c optimized cx16 txt screen functions to use VERA autoincrement 2020-09-21 19:30:21 +02:00
ca9588380a added cx16 txt.clear_screencolors 2020-09-21 18:42:28 +02:00
ae2619602d lib renames in docs 2020-09-21 18:21:24 +02:00
de06353194 auto select correct library to import based on target, instead of having c64- and cx16- prefix variants
some programs are now 100% source compatible between C64 and Cx16 targets!
import libraries have been rena;med
2020-09-21 00:50:09 +02:00
3ff3f5e1cc compiler errors in standard format so that you can click on them in IDE to jump to the line 2020-09-20 22:24:35 +02:00
4b747859b3 types of constant values now actually follow their declared const var type 2020-09-20 01:14:53 +02:00
2201765366 mult fixes 2020-09-20 00:17:33 +02:00
dfa1d5e398 removed the ".w" word suffix (it confused the parser). 2020-09-19 23:27:40 +02:00
ce9a90f626 updates to make c16txtio more complete 2020-09-19 23:00:47 +02:00
2deb18beb2 tweaks to c64 txtio. Fixed expression evaluation of bitwise invert. 2020-09-19 22:37:24 +02:00
0f7454059c tweaks to c64 txtio 2020-09-19 22:10:33 +02:00
f9ba09ac4d todo 2020-09-19 17:39:46 +02:00
4e74873eae better swap() code 2020-09-19 17:32:29 +02:00
f0cd03d14f removed invalid duplicate name check about subroutine parameters 2020-09-19 16:04:04 +02:00
f2b069c562 correction, we don't allow address-of as a value for memory mapped vars, improved the error message instead 2020-09-19 15:54:42 +02:00
bc89306dc1 better detection of duplicate variable definitions 2020-09-19 15:46:51 +02:00
bf4da1655b doc 2020-09-18 23:57:40 +02:00
d819aa270f test 2020-09-18 23:38:50 +02:00
e6d945f835 doc 2020-09-18 23:35:02 +02:00
4fe408f1fd doc 2020-09-18 23:34:32 +02:00
c376e42092 implemented hidden line removal 2020-09-18 23:15:08 +02:00
63a653cdf0 preparing for hidden line removal 2020-09-18 22:51:44 +02:00
5d900800f2 vardecl value inits must not be shuffled around but stay at their original line at all times 2020-09-18 22:24:26 +02:00
def06dbc0b allow address-of to be used as a value for a memory pointer variable 2020-09-18 22:10:20 +02:00
9b66a597bb array literal const check added 2020-09-18 21:30:59 +02:00
f1ee3b4e60 version 4.2 2020-09-16 23:04:18 +02:00
6395e39d63 avoid generating superfluous '0' variable initializations, and fix erroneous vardecl order shifting 2020-09-16 22:15:06 +02:00
2a6d9d7e31 more optimal codegen for some typecasts 2020-09-15 03:26:57 +02:00
32a7cd31da more optimal codegen for if statements 2020-09-15 00:31:44 +02:00
dd4a56cb5f cx16 safe clobbers for now 2020-09-15 00:14:36 +02:00
d110d1cb5f c64 system reset now banks kernel rom back in 2020-09-15 00:10:20 +02:00
48858019b7 added the last of the optimized mul_word asm routines 2020-09-14 23:54:01 +02:00
aff6b1fca5 added some more optimized mul_word asm routines 2020-09-14 23:03:18 +02:00
d260182ef3 added some more optimized mul_byte asm routines 2020-09-14 22:06:40 +02:00
e39a38b0d9 things 2020-09-13 21:04:51 +02:00
82d7179c92 printf now uses proper zp addressing 2020-09-13 21:01:19 +02:00
f42746ba06 reg_x removal: c64textio and c64lib. last one. 2020-09-13 20:52:29 +02:00
1f69deaccd reg_x removal: c64floats 2020-09-13 20:44:55 +02:00
ea8b7ab193 reg_x removal: math.asm and some others 2020-09-13 20:38:50 +02:00
9938959026 reg_x removal: prog8lib 2020-09-13 20:25:30 +02:00
d5e5485d2e fixed estack X corruption in float augmented assignments 2020-09-13 19:44:03 +02:00
97b9c8f320 don't clobber A when trying to save X at functioncall 2020-09-12 19:04:44 +02:00
35aebbc209 optimize unneeded type casts for register args 2020-09-12 02:48:16 +02:00
81f7419f70 fix X register clobbering in asmfunc call, fixed graphics.plot() 2020-09-12 01:23:56 +02:00
2f951bd54d tweaking cobra mk3 2020-09-11 19:46:11 +02:00
18f5963b09 cobra mk3 2020-09-10 01:31:21 +02:00
836509c1d1 mult todos. 2020-09-10 00:53:35 +02:00
949d536e42 mult todo's. Fixed wrong compilation target when compiling multiple files at once. 2020-09-10 00:26:35 +02:00
f69b17e165 mult todo's 2020-09-10 00:07:06 +02:00
49a0584c54 added a %target directive 2020-09-09 22:53:34 +02:00
e21aa2c8f0 better naming of the optimized math mult routines 2020-09-09 22:16:37 +02:00
40071b1431 fix compiler crash with adding too many typecasts to args. useless lsb() and msb() are optimized away. 2020-09-09 21:37:56 +02:00
02e29e6990 added some preliminary clobber specs to some cx16 graphics calls, This fixes the 3d cube gfx 2020-09-07 04:06:46 +02:00
e19de0901e Fix cx16 system reset. Added cx16 VIA registers. Fix cx16 VERA register widths. 2020-09-07 03:09:09 +02:00
137d506e42 improve register arg passing again 2020-09-07 02:29:03 +02:00
90c4a26d52 we don't implement asmsub params via @stack yet 2020-09-07 01:24:10 +02:00
f378a8997b improved ability to use register X in asm subroutine fuction arguments 2020-09-07 00:25:51 +02:00
1377bed988 fix assembly for cx16 when zp is not basicsafe 2020-09-06 17:58:05 +02:00
8f9f947c42 fix some issues with float const 0.0 and 1.0 2020-09-05 02:07:41 +02:00
37f6c2858f warning about attempt to put floats in zp 2020-09-05 01:45:58 +02:00
13d7f239ab floating point 1.0 no longer referenced from ROM because cx16 doesn't have it. Added some more cx16 examples. 2020-09-05 00:17:58 +02:00
a6f3c84e28 fix cx16 word sign extend in bitshift 2020-09-04 22:38:03 +02:00
fe4e0e9835 cleanups 2020-08-31 23:00:53 +02:00
809917f13b version 4.1 2020-08-31 21:44:38 +02:00
2b35498370 added CX16 txt.setcc and swirl examples that use it 2020-08-31 21:01:18 +02:00
f45eabdd9e added CX16 VERA registers, made txt.fill_screen work on CX16 2020-08-31 18:23:52 +02:00
438f3ee8d2 make GIVUAYFAY work (unsigned word to float) 2020-08-31 17:16:51 +02:00
4bea31f051 fl_zero fix 2020-08-31 01:04:04 +02:00
5eae7a2b93 tweak mandelbrots and c64 graphics plot() doesnt work with XY parameter 2020-08-31 00:36:40 +02:00
364ef3e55c tweak cx16 mandelbrots 2020-08-31 00:03:05 +02:00
e61818f194 tweak cx16 mandelbrots 2020-08-30 19:31:20 +02:00
0f9ce319d4 readme 2020-08-30 18:36:02 +02:00
5d90871789 got floating points working in commanderx16, added txt.color() to set text color 2020-08-30 00:15:18 +02:00
88a9e09918 got floating points working in commanderx16 2020-08-29 23:55:26 +02:00
c50ecf6055 fix for loop asm creation with word loopvar 2020-08-29 02:05:24 +02:00
a18de75da9 fix compiler loop and missing type checks on for loop range values 2020-08-29 01:48:41 +02:00
e112dfd910 implemented signed byte and word division 2020-08-29 00:00:53 +02:00
9154d8bd37 optimizing X register saving for 65c02 using phx/plx instead of zp location 2020-08-28 22:11:33 +02:00
0b55372b3b cleanup cx16 things and added call signatures. c64graphics moved into built-in libraries. 2020-08-28 21:42:53 +02:00
3ad7fb010f clearer about emulator 2020-08-27 21:09:59 +02:00
3f64d1bb5a oops. 2020-08-27 21:04:08 +02:00
a6f564ad88 version 4.0 2020-08-27 20:54:08 +02:00
d97da3bb7b implemented almost all math operations 2020-08-27 20:47:22 +02:00
a77d3c92ad implemented remaining float operations 2020-08-27 19:47:50 +02:00
6d17e5307c fixed typecasting of const arguments once again 2020-08-27 19:06:27 +02:00
c2205e473a fix example 2020-08-27 18:21:12 +02:00
4ffb194847 readme and version 2020-08-27 18:18:29 +02:00
744cd6ec42 updated examples 2020-08-27 18:11:49 +02:00
f08fc18ab5 renamed c64scr. to txt. 2020-08-27 18:10:22 +02:00
462af76770 cx16 link 2020-08-26 20:54:36 +02:00
9cec554f7c moved the type conversion routines to their own library file to avoid duplication 2020-08-26 20:52:38 +02:00
08b25e610d commander x16 improvements 2020-08-26 19:34:12 +02:00
e896d5a1a6 ver 2020-08-26 02:03:18 +02:00
b939562062 added preliminary CommanderX16 machine target support. Fixed nullpointer when importing a missing file. 2020-08-26 01:56:26 +02:00
256781bba5 added missing in-place bitwise operator code 2020-08-25 22:26:05 +02:00
19705196d6 separate varnames and other symbol names 2020-08-25 22:08:52 +02:00
3ce692bb10 even better machinetarget independence 2020-08-25 19:56:53 +02:00
78bdbde3ae refer to ZP scratch constants from asm code via the global P8ZP constants as well 2020-08-25 19:44:08 +02:00
8d8c066447 made the ZP and compilation target more generic 2020-08-25 19:32:31 +02:00
5da9379c37 making zeropage more configurable for future different machine targets 2020-08-25 18:10:06 +02:00
032d20ff37 added the missing stack assignments 2020-08-25 17:43:35 +02:00
d19b17cbfe optimize strlen() 2020-08-25 17:31:47 +02:00
4a4f8ff5db subroutine parameters can be allocated on the zp now as well 2020-08-25 16:47:21 +02:00
60a9209a14 plasma 2020-08-25 01:48:23 +02:00
0f9e167df3 proper name 2020-08-25 00:59:02 +02:00
2e2b8c498e slightly optimize loop 2020-08-25 00:35:51 +02:00
144199730f refactored and optimized load/store byte from pointervar 2020-08-25 00:18:33 +02:00
4bb4eab3b2 cleanup 2020-08-24 23:18:46 +02:00
cf9151f669 use AsmAssignment preferrably over creating new ast node for codegen 2020-08-24 22:45:43 +02:00
aef4598cec comments 2020-08-24 02:56:22 +02:00
3ada0fdf84 function call register args code consolidation, fix asm for loading word value from variable into register 2020-08-24 01:42:44 +02:00
a5d97b326e bugfix byte array assignment 2020-08-24 00:48:19 +02:00
2640015fb1 move 2020-08-24 00:26:26 +02:00
6cd42ddafe cleanup 2020-08-23 23:28:25 +02:00
1f17c22132 more array access optimizations 2020-08-23 22:36:49 +02:00
5c62f612cc cleanup 2020-08-23 20:34:27 +02:00
b9ca1c2e2c more uniform code for array indexing (all using scaled offset now) 2020-08-23 20:25:00 +02:00
93b2ff2e52 fix postincrdecr on array value 2020-08-23 18:52:19 +02:00
3991d23a69 refactoring 2020-08-23 18:20:57 +02:00
1be139759c better names 2020-08-23 16:08:31 +02:00
d0674ad688 better names, reorder 2020-08-23 14:36:24 +02:00
ffb47458ff better names 2020-08-23 13:56:21 +02:00
84ec1be8a4 assign type relax 2020-08-23 13:31:14 +02:00
f4dafec645 assign type assert 2020-08-23 12:52:27 +02:00
97ce72521d for arrays, use the element's datatype more instead of the array decl type 2020-08-23 12:03:52 +02:00
d2f0e74879 use sourcetype 2020-08-23 11:31:33 +02:00
d9e3895c45 start with yet another codegen restructure, this time to make the assignment of values even more explicit for the codegen 2020-08-23 02:05:01 +02:00
5075901830 work 2020-08-22 23:39:27 +02:00
f1193bb5a0 Better error message 2020-08-22 23:13:53 +02:00
d3dc279105 updated the compiled examples 2020-08-22 22:57:30 +02:00
acc942f690 added some more asm code optimizations by splitting certain assignments 2020-08-22 22:53:21 +02:00
e947067dcf fixed source code output issue 2020-08-22 22:23:00 +02:00
bd9ebf4603 flipped the order of the parameters of mkword() so it's now mkword(msb, lsb) for easier readability 2020-08-22 21:13:38 +02:00
f41192a52a added cube3d-gfx example 2020-08-22 19:00:03 +02:00
ff54d6abd7 reorder const for all associative operators 2020-08-22 17:44:32 +02:00
f40bcc219f better errormsg 2020-08-22 17:29:35 +02:00
679965410a todo 2020-08-22 17:13:23 +02:00
c6e13ae2a3 better error message 2020-08-22 17:12:09 +02:00
20cdcc673b identifiers can no longer start with an underscore. (this interfered with 64tass syntax) 2020-08-22 17:03:40 +02:00
89f46222d9 fix compiler crash when calling a non-subroutine 2020-08-22 17:01:47 +02:00
b27cbfac5e removed lsl() and lsr() functions just use <<=1 and >>=1 2020-08-22 16:44:48 +02:00
31c946aeeb bugfix 2020-08-22 16:39:17 +02:00
bfc8a26381 implemented bit shifting for non-const amounts 2020-08-22 16:13:52 +02:00
9d98746501 version 3.2 2020-08-21 18:02:49 +02:00
63b03ba70c fix typecasting 2020-08-21 18:02:01 +02:00
70bab76b36 added plasma example 2020-08-21 17:58:43 +02:00
15d24d4308 adding plasma example 2020-08-21 17:27:18 +02:00
9ec62eb045 fixed lsb(), fixed const value type mismatch, fixed and() const evaluation. 2020-08-21 16:26:40 +02:00
12f841e30d just prints 2020-08-21 09:25:32 +02:00
335599ed22 restored certain memoryread asm gen 2020-08-21 07:44:50 +02:00
0b717f9e76 clear messages about slow expression code generation points 2020-08-21 05:45:39 +02:00
e941f6ecca fix asm bug 2020-08-21 04:23:08 +02:00
ef7744dbda asm fix 2020-08-21 04:02:10 +02:00
c83a61c460 some float asm code added for in-place 2020-08-21 03:06:37 +02:00
335684caf7 don't remove asmsub definitions... 2020-08-21 03:01:07 +02:00
8d6220ce51 added most essential of the new in-place assignment code 2020-08-21 02:17:40 +02:00
39ea5c5f99 fix parse error for <<= and >>= 2020-08-20 23:24:01 +02:00
b03597ac13 fixed bug in operand equality comparison, could lead to compiler endless loop 2020-08-20 22:21:26 +02:00
58f323c087 implemented missing memory postincrdecr codegen 2020-08-20 21:48:15 +02:00
513a68584c implemented more optimized prefix expression codegen 2020-08-20 21:42:38 +02:00
88d5c68b32 don't inc/dec a memory mapped register 2020-08-20 21:16:48 +02:00
14f9382cf9 typecheck prefix expressions better 2020-08-20 20:46:28 +02:00
cffb582568 added start of optimized in-place assignment code (for prefix expressions) 2020-08-20 18:43:10 +02:00
e1812ce16c fix typecast removal error. 2020-08-20 18:07:48 +02:00
7a3163f59a bugfix in direct memory assignment 2020-08-20 17:02:22 +02:00
6f3b2749b0 refactoring assignments codegen 2020-08-20 16:47:43 +02:00
c144d4e501 improved warnings about unreachable code 2020-08-20 14:28:17 +02:00
edfd9d55ba added sizeof() function 2020-08-20 13:50:28 +02:00
774897260e avoid silent type casts that remove precision (such as float -> word) 2020-08-20 12:49:48 +02:00
65ba91411d improved function arg type checking and error message 2020-08-20 12:38:22 +02:00
9cbb8e1a64 version 3.1 2020-08-18 16:26:23 +02:00
53e9ad5088 better asm code for repeat loops 2020-08-18 16:02:40 +02:00
cf6ea63fa6 forloop asm done 2020-08-18 15:29:39 +02:00
1de0ebb7bc more forloop asm 2020-08-18 15:16:56 +02:00
77c1376d6d proper error message for arrays that are declared too big 2020-08-18 14:47:52 +02:00
353f1954a5 for loop codegen 2020-08-18 14:03:31 +02:00
8bf3406cf8 gradle version 2020-08-18 00:53:14 +02:00
936bf9a05c gradle version 2020-08-18 00:47:23 +02:00
4487499663 more forloop codegen 2020-08-17 23:42:43 +02:00
3976cc26a2 more forloop codegen 2020-08-17 23:19:23 +02:00
e6ff87ecd0 upgraded to Kotlin 1.4, fixed several compilation warnings 2020-08-17 19:36:07 +02:00
c0887b5f08 removed 'continue' statement to be able to generate more optimized loop assembly code. started with for loop optimizations 2020-08-17 19:22:29 +02:00
f14dda4eca fix certain corruption of A register argument on asm sub call 2020-08-16 19:15:44 +02:00
bd7f75c130 loop todos 2020-07-30 02:54:37 +02:00
fbe3ce008b slight expression rewrite in case of certain in-place assignments, to try to get the in-place variable operand to the leftmost position 2020-07-30 01:30:21 +02:00
7ac6c8f2d1 todo related to in-place assignment 2020-07-27 00:32:59 +02:00
fdfbb7bdf0 improved call arguments type check 2020-07-27 00:28:48 +02:00
1c16bbb742 tweaks for string handling as arguments 2020-07-27 00:12:27 +02:00
9735527062 cleanup double code 2020-07-26 23:46:06 +02:00
402827497e fix float array assignment 2020-07-26 23:32:20 +02:00
f81aa0d867 Merge branch 'remove_aug_assign' 2020-07-26 19:23:34 +02:00
d32a970101 partly optimize assignments so that simple increments and decrements can be done via separate statements (postincrdecr) 2020-07-26 19:22:12 +02:00
cd651aa416 use repeat 2020-07-26 13:50:14 +02:00
8a3189123a to reduce complexity, augmented assignment has been removed again from internal Ast and codegen for now. 2020-07-26 13:48:31 +02:00
b37231d0f5 version 3.0 2020-07-26 01:33:02 +02:00
3c55719bf1 finalize repeat asmgen 2020-07-26 01:32:27 +02:00
af8279a9b9 empty for loops are removed 2020-07-25 22:54:50 +02:00
c38508c262 introduced repeat loop. repeat-until changed to do-util.
forever loop is gone (use repeat without iteration count).
struct literal is now same as array literal [...] to avoid parsing ambiguity with scope blocks.
2020-07-25 16:56:34 +02:00
b0e8738ab8 remove unused c64 resources 2020-07-25 14:47:31 +02:00
cae480768e version is work in progress 2020-07-25 14:45:06 +02:00
a70276c190 use indexOfFirst. Also avoid initializing a for loop variable twice in a row. 2020-07-25 14:44:24 +02:00
0c461ffe2e removed Register expression (directly accessing cpu register) 2020-07-25 14:14:24 +02:00
237511f2d6 v2.4 2020-07-04 18:56:47 +02:00
cdcb652033 optimized arg passing if all args are registers 2020-07-04 18:56:30 +02:00
71e678b382 fixed possible register subroutine arg clobbering 2020-07-04 17:05:36 +02:00
3050156325 reverted subroutine inlining, it was a mistake 2020-07-04 01:02:36 +02:00
181 changed files with 16810 additions and 11098 deletions

View File

@ -4,8 +4,8 @@ sudo: false
# dist: xenial # dist: xenial
before_install: before_install:
- chmod +x gradlew - chmod +x ./gradlew
script: script:
- gradle test - ./gradlew test

View File

@ -2,23 +2,32 @@
[![Build Status](https://travis-ci.org/irmen/prog8.svg?branch=master)](https://travis-ci.org/irmen/prog8) [![Build Status](https://travis-ci.org/irmen/prog8.svg?branch=master)](https://travis-ci.org/irmen/prog8)
[![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](https://prog8.readthedocs.io/) [![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](https://prog8.readthedocs.io/)
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
=========================================================================== ============================================================================
*Written by Irmen de Jong (irmen@razorvine.net)* *Written by Irmen de Jong (irmen@razorvine.net)*
*Software license: GNU GPL 3.0, see file LICENSE* *Software license: GNU GPL 3.0, see file LICENSE*
This is a structured programming language for the 8-bit 6502/6510 microprocessor from the late 1970's and 1980's This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
as used in many home computers from that era. It is a medium to low level programming language, as used in many home computers from that era. It is a medium to low level programming language,
which aims to provide many conveniences over raw assembly code (even when using a macro assembler): which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
- reduction of source code length Documentation
-------------
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
https://prog8.readthedocs.io/
What use Prog8 provide?
-----------------------
- reduction of source code length over raw assembly
- modularity, symbol scoping, subroutines - modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings) - various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string and array variables and string sharing - automatic variable allocations, automatic string and array variables and string sharing
- subroutines with a input- and output parameter signature - subroutines with an input- and output parameter signature
- constant folding in expressions - constant folding in expressions
- conditional branches - conditional branches
- 'when' statement to provide a concise jump table alternative to if/elseif chains - 'when' statement to provide a concise jump table alternative to if/elseif chains
@ -29,7 +38,7 @@ which aims to provide many conveniences over raw assembly code (even when using
- inline assembly allows you to have full control when every cycle or byte matters - inline assembly allows you to have full control when every cycle or byte matters
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse`` - many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
Rapid edit-compile-run-debug cycle: *Rapid edit-compile-run-debug cycle:*
- use a modern PC to do the work on - use a modern PC to do the work on
- very quick compilation times - very quick compilation times
@ -37,15 +46,16 @@ Rapid edit-compile-run-debug cycle:
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them - breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly - source code labels automatically loaded in Vice emulator so it can show them in disassembly
Prog8 is mainly targeted at the Commodore-64 machine at this time. *Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
Contributions to add support for other 8-bit (or other?!) machines are welcome.
Documentation/manual - "c64": Commodore-64 (6510 CPU = almost a 6502) premium support.
-------------------- - "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) experimental support.
https://prog8.readthedocs.io/ - 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)!
Required tools
--------------
Additional required tools
-------------------------
[64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path. [64tass](https://sourceforge.net/projects/tass64/) - cross assembler. Install this on your shell path.
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project. A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
@ -55,8 +65,9 @@ A **Java runtime (jre or jdk), version 8 or newer** is required to run a prepac
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance, If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
IntelliJ IDEA with the Kotlin plugin). IntelliJ IDEA with the Kotlin plugin).
It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence
of the [Vice emulator](http://vice-emu.sourceforge.net/) of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
Example code Example code
@ -64,44 +75,45 @@ Example code
This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64utils %import textio
%zeropage basicsafe %zeropage basicsafe
main { main {
ubyte[256] sieve ubyte[256] sieve
ubyte candidate_prime = 2 ubyte candidate_prime = 2 ; is increased in the loop
sub start() { sub start() {
memset(sieve, 256, false) ; clear the sieve, to reset starting situation on subsequent runs
memset(sieve, 256, false)
c64scr.print("prime numbers up to 255:\n\n") ; calculate primes
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0 ubyte amount=0
while true { repeat {
ubyte prime = find_next_prime() ubyte prime = find_next_prime()
if prime==0 if prime==0
break break
c64scr.print_ub(prime) txt.print_ub(prime)
c64scr.print(", ") txt.print(", ")
amount++ amount++
} }
c64.CHROUT('\n') txt.chrout('\n')
c64scr.print("number of primes (expected 54): ") txt.print("number of primes (expected 54): ")
c64scr.print_ub(amount) txt.print_ub(amount)
c64.CHROUT('\n') txt.chrout('\n')
} }
sub find_next_prime() -> ubyte { sub find_next_prime() -> ubyte {
while sieve[candidate_prime] { while sieve[candidate_prime] {
candidate_prime++ candidate_prime++
if candidate_prime==0 if candidate_prime==0
return 0 return 0 ; we wrapped; no more primes available in the sieve
} }
; found next one, mark the multiples and return it.
sieve[candidate_prime] = true sieve[candidate_prime] = true
uword multiple = candidate_prime uword multiple = candidate_prime
while multiple < len(sieve) { while multiple < len(sieve) {
sieve[lsb(multiple)] = true sieve[lsb(multiple)] = true
multiple += candidate_prime multiple += candidate_prime
@ -111,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: when compiled an ran on a C-64 you'll get:
![c64 screen](docs/source/_static/primes_example.png) ![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: One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this:
![wizzine screen](docs/source/_static/wizzine.png) ![wizzine screen](docs/source/_static/wizzine.png)
@ -127,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: 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) ![tehtriz_screen](docs/source/_static/tehtriz.png)
The CommanderX16 compiler target is quite capable already too, here's a well known space ship
animated in 3D with hidden line removal, in the CommanderX16 emulator:
![cobra3d](docs/source/_static/cobra3d.png)

View File

@ -1,11 +1,11 @@
buildscript { buildscript {
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
} }
} }
plugins { plugins {
// id "org.jetbrains.kotlin.jvm" version "1.3.72" // id "org.jetbrains.kotlin.jvm" version "1.4.10"
id 'application' id 'application'
id 'org.jetbrains.dokka' version "0.9.18" id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.2.0' id 'com.github.johnrengelman.shadow' version '5.2.0'
@ -110,3 +110,7 @@ dokka {
outputFormat = 'html' outputFormat = 'html'
outputDirectory = "$buildDir/kdoc" outputDirectory = "$buildDir/kdoc"
} }
task wrapper(type: Wrapper) {
gradleVersion = '6.1.1'
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -1,49 +1,56 @@
; --- low level floating point assembly routines for the C64 ; --- low level floating point assembly routines for the C64
FL_ONE_const .byte 129 ; 1.0
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
floats_store_reg .byte 0 ; temp storage
ub2float .proc ub2float .proc
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y ; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y ; clobbers A, Y
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
sta c64.SCRATCH_ZPWORD2 sta P8ZP_SCRATCH_W2
sty c64.SCRATCH_ZPWORD2+1 sty P8ZP_SCRATCH_W2+1
ldy c64.SCRATCH_ZPB1 ldy P8ZP_SCRATCH_B1
jsr FREADUY lda #0
_fac_to_mem ldx c64.SCRATCH_ZPWORD2 jsr GIVAYF
ldy c64.SCRATCH_ZPWORD2+1 _fac_to_mem ldx P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
jsr MOVMF jsr MOVMF
ldx c64.SCRATCH_ZPREGX ldx P8ZP_SCRATCH_REG
rts rts
.pend .pend
b2float .proc b2float .proc
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y ; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y ; clobbers A, Y
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
sta c64.SCRATCH_ZPWORD2 sta P8ZP_SCRATCH_W2
sty c64.SCRATCH_ZPWORD2+1 sty P8ZP_SCRATCH_W2+1
lda c64.SCRATCH_ZPB1 lda P8ZP_SCRATCH_B1
jsr FREADSA jsr FREADSA
jmp ub2float._fac_to_mem jmp ub2float._fac_to_mem
.pend .pend
uw2float .proc uw2float .proc
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y ; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
sta c64.SCRATCH_ZPWORD2 sta P8ZP_SCRATCH_W2
sty c64.SCRATCH_ZPWORD2+1 sty P8ZP_SCRATCH_W2+1
lda c64.SCRATCH_ZPWORD1 lda P8ZP_SCRATCH_W1
ldy c64.SCRATCH_ZPWORD1+1 ldy P8ZP_SCRATCH_W1+1
jsr GIVUAYFAY jsr GIVUAYFAY
jmp ub2float._fac_to_mem jmp ub2float._fac_to_mem
.pend .pend
w2float .proc w2float .proc
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y ; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
sta c64.SCRATCH_ZPWORD2 sta P8ZP_SCRATCH_W2
sty c64.SCRATCH_ZPWORD2+1 sty P8ZP_SCRATCH_W2+1
ldy c64.SCRATCH_ZPWORD1 ldy P8ZP_SCRATCH_W1
lda c64.SCRATCH_ZPWORD1+1 lda P8ZP_SCRATCH_W1+1
jsr GIVAYF jsr GIVAYF
jmp ub2float._fac_to_mem jmp ub2float._fac_to_mem
.pend .pend
@ -51,8 +58,8 @@ w2float .proc
stack_b2float .proc stack_b2float .proc
; -- b2float operating on the stack ; -- b2float operating on the stack
inx inx
lda c64.ESTACK_LO,x lda P8ESTACK_LO,x
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr FREADSA jsr FREADSA
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -60,9 +67,9 @@ stack_b2float .proc
stack_w2float .proc stack_w2float .proc
; -- w2float operating on the stack ; -- w2float operating on the stack
inx inx
ldy c64.ESTACK_LO,x ldy P8ESTACK_LO,x
lda c64.ESTACK_HI,x lda P8ESTACK_HI,x
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr GIVAYF jsr GIVAYF
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -70,44 +77,45 @@ stack_w2float .proc
stack_ub2float .proc stack_ub2float .proc
; -- ub2float operating on the stack ; -- ub2float operating on the stack
inx inx
lda c64.ESTACK_LO,x lda P8ESTACK_LO,x
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
tay tay
jsr FREADUY lda #0
jsr GIVAYF
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
stack_uw2float .proc stack_uw2float .proc
; -- uw2float operating on the stack ; -- uw2float operating on the stack
inx inx
lda c64.ESTACK_LO,x lda P8ESTACK_LO,x
ldy c64.ESTACK_HI,x ldy P8ESTACK_HI,x
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY jsr GIVUAYFAY
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
stack_float2w .proc ; also used for float2b stack_float2w .proc ; also used for float2b
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr AYINT jsr AYINT
ldx c64.SCRATCH_ZPREGX ldx P8ZP_SCRATCH_REG
lda $64 lda $64
sta c64.ESTACK_HI,x sta P8ESTACK_HI,x
lda $65 lda $65
sta c64.ESTACK_LO,x sta P8ESTACK_LO,x
dex dex
rts rts
.pend .pend
stack_float2uw .proc ; also used for float2ub stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr GETADR jsr GETADR
ldx c64.SCRATCH_ZPREGX ldx P8ZP_SCRATCH_REG
sta c64.ESTACK_HI,x sta P8ESTACK_HI,x
tya tya
sta c64.ESTACK_LO,x sta P8ESTACK_LO,x
dex dex
rts rts
.pend .pend
@ -115,79 +123,68 @@ stack_float2uw .proc ; also used for float2ub
push_float .proc push_float .proc
; ---- push mflpt5 in A/Y onto stack ; ---- push mflpt5 in A/Y onto stack
; (taking 3 stack positions = 6 bytes of which 1 is padding) ; (taking 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1 sta P8ZP_SCRATCH_W1
sty c64.SCRATCH_ZPWORD1+1 sty P8ZP_SCRATCH_W1+1
ldy #0 ldy #0
lda (c64.SCRATCH_ZPWORD1),y lda (P8ZP_SCRATCH_W1),y
sta c64.ESTACK_LO,x sta P8ESTACK_LO,x
iny iny
lda (c64.SCRATCH_ZPWORD1),y lda (P8ZP_SCRATCH_W1),y
sta c64.ESTACK_HI,x sta P8ESTACK_HI,x
dex dex
iny iny
lda (c64.SCRATCH_ZPWORD1),y lda (P8ZP_SCRATCH_W1),y
sta c64.ESTACK_LO,x sta P8ESTACK_LO,x
iny iny
lda (c64.SCRATCH_ZPWORD1),y lda (P8ZP_SCRATCH_W1),y
sta c64.ESTACK_HI,x sta P8ESTACK_HI,x
dex dex
iny iny
lda (c64.SCRATCH_ZPWORD1),y lda (P8ZP_SCRATCH_W1),y
sta c64.ESTACK_LO,x sta P8ESTACK_LO,x
dex dex
rts rts
.pend .pend
func_rndf .proc func_rndf .proc
; -- put a random floating point value on the stack ; -- put a random floating point value on the stack
stx c64.SCRATCH_ZPREG stx P8ZP_SCRATCH_REG
lda #1 lda #1
jsr FREADSA jsr FREADSA
jsr RND ; rng into fac1 jsr RND ; rng into fac1
ldx #<_rndf_rnum5 ldx #<_rndf_rnum5
ldy #>_rndf_rnum5 ldy #>_rndf_rnum5
jsr MOVMF ; fac1 to mem X/Y jsr MOVMF ; fac1 to mem X/Y
ldx c64.SCRATCH_ZPREG ldx P8ZP_SCRATCH_REG
lda #<_rndf_rnum5 lda #<_rndf_rnum5
ldy #>_rndf_rnum5 ldy #>_rndf_rnum5
jmp push_float jmp push_float
_rndf_rnum5 .byte 0,0,0,0,0 _rndf_rnum5 .byte 0,0,0,0,0
.pend .pend
push_float_from_indexed_var .proc
; -- push the float from the array at A/Y with index on stack, onto the stack.
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp push_float
.pend
pop_float .proc pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y ; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding) ; (frees 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1 sta P8ZP_SCRATCH_W1
sty c64.SCRATCH_ZPWORD1+1 sty P8ZP_SCRATCH_W1+1
ldy #4 ldy #4
inx inx
lda c64.ESTACK_LO,x lda P8ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
dey dey
inx inx
lda c64.ESTACK_HI,x lda P8ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
dey dey
lda c64.ESTACK_LO,x lda P8ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
dey dey
inx inx
lda c64.ESTACK_HI,x lda P8ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
dey dey
lda c64.ESTACK_LO,x lda P8ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
rts rts
.pend .pend
@ -201,110 +198,74 @@ pop_float_fac1 .proc
jmp MOVFM jmp MOVFM
.pend .pend
pop_float_fac2 .proc
; -- pops float from stack into FAC2
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp CONUPK
.pend
pop_float_to_indexed_var .proc pop_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 ; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta c64.SCRATCH_ZPWORD1 sta P8ZP_SCRATCH_W1
sty c64.SCRATCH_ZPWORD1+1 sty P8ZP_SCRATCH_W1+1
jsr prog8_lib.pop_index_times_5 jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1 lda P8ZP_SCRATCH_W1
ldy c64.SCRATCH_ZPWORD1+1 ldy P8ZP_SCRATCH_W1+1
jmp pop_float jmp pop_float
.pend .pend
copy_float .proc copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1, ; -- 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. ; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta c64.SCRATCH_ZPWORD2 sta _target+1
sty c64.SCRATCH_ZPWORD2+1 sty _target+2
ldy #0 ldy #4
lda (c64.SCRATCH_ZPWORD1),y _loop lda (P8ZP_SCRATCH_W1),y
sta (c64.SCRATCH_ZPWORD2),y _target sta $ffff,y ; modified
iny dey
lda (c64.SCRATCH_ZPWORD1),y bpl _loop
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
rts rts
.pend .pend
inc_var_f .proc inc_var_f .proc
; -- add 1 to float pointed to by A/Y ; -- add 1 to float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1 sta P8ZP_SCRATCH_W1
sty c64.SCRATCH_ZPWORD1+1 sty P8ZP_SCRATCH_W1+1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr MOVFM jsr MOVFM
lda #<FL_FONE lda #<FL_ONE_const
ldy #>FL_FONE ldy #>FL_ONE_const
jsr FADD jsr FADD
ldx c64.SCRATCH_ZPWORD1 ldx P8ZP_SCRATCH_W1
ldy c64.SCRATCH_ZPWORD1+1 ldy P8ZP_SCRATCH_W1+1
jsr MOVMF jsr MOVMF
ldx c64.SCRATCH_ZPREGX ldx P8ZP_SCRATCH_REG
rts rts
.pend .pend
dec_var_f .proc dec_var_f .proc
; -- subtract 1 from float pointed to by A/Y ; -- subtract 1 from float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1 sta P8ZP_SCRATCH_W1
sty c64.SCRATCH_ZPWORD1+1 sty P8ZP_SCRATCH_W1+1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
lda #<FL_FONE lda #<FL_ONE_const
ldy #>FL_FONE ldy #>FL_ONE_const
jsr MOVFM jsr MOVFM
lda c64.SCRATCH_ZPWORD1 lda P8ZP_SCRATCH_W1
ldy c64.SCRATCH_ZPWORD1+1 ldy P8ZP_SCRATCH_W1+1
jsr FSUB jsr FSUB
ldx c64.SCRATCH_ZPWORD1 ldx P8ZP_SCRATCH_W1
ldy c64.SCRATCH_ZPWORD1+1 ldy P8ZP_SCRATCH_W1+1
jsr MOVMF jsr MOVMF
ldx c64.SCRATCH_ZPREGX ldx P8ZP_SCRATCH_REG
rts rts
.pend .pend
inc_indexed_var_f .proc
; -- add 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp inc_var_f
.pend
dec_indexed_var_f .proc
; -- subtract 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp dec_var_f
.pend
pop_2_floats_f2_in_fac1 .proc pop_2_floats_f2_in_fac1 .proc
; -- pop 2 floats from stack, load the second one in FAC1 as well ; -- pop 2 floats from stack, load the second one in FAC1 as well
@ -330,7 +291,7 @@ push_fac1_as_result .proc
jsr MOVMF jsr MOVMF
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
ldx c64.SCRATCH_ZPREGX ldx P8ZP_SCRATCH_REG
jmp push_float jmp push_float
.pend .pend
@ -342,21 +303,21 @@ pow_f .proc
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr pop_float jsr pop_float
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr CONUPK ; fac2 = float1 jsr CONUPK ; fac2 = float1
lda #<fmath_float2 lda #<fmath_float2
ldy #>fmath_float2 ldy #>fmath_float2
jsr FPWR jsr FPWR
ldx c64.SCRATCH_ZPREGX ldx P8ZP_SCRATCH_REG
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
div_f .proc div_f .proc
; -- push f1/f2 on stack ; -- push f1/f2 on stack
jsr pop_2_floats_f2_in_fac1 jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr FDIV jsr FDIV
@ -366,7 +327,7 @@ div_f .proc
add_f .proc add_f .proc
; -- push f1+f2 on stack ; -- push f1+f2 on stack
jsr pop_2_floats_f2_in_fac1 jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr FADD jsr FADD
@ -376,7 +337,7 @@ add_f .proc
sub_f .proc sub_f .proc
; -- push f1-f2 on stack ; -- push f1-f2 on stack
jsr pop_2_floats_f2_in_fac1 jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr FSUB jsr FSUB
@ -386,7 +347,7 @@ sub_f .proc
mul_f .proc mul_f .proc
; -- push f1*f2 on stack ; -- push f1*f2 on stack
jsr pop_2_floats_f2_in_fac1 jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr FMULT jsr FMULT
@ -394,19 +355,19 @@ mul_f .proc
.pend .pend
neg_f .proc neg_f .proc
; -- push -flt back on stack ; -- toggle the sign bit on the stack
jsr pop_float_fac1 lda P8ESTACK_HI+3,x
stx c64.SCRATCH_ZPREGX eor #$80
jsr NEGOP sta P8ESTACK_HI+3,x
jmp push_fac1_as_result rts
.pend .pend
abs_f .proc abs_f .proc
; -- push abs(float) on stack (as float) ; -- strip the sign bit on the stack
jsr pop_float_fac1 lda P8ESTACK_HI+3,x
stx c64.SCRATCH_ZPREGX and #$7f
jsr ABS sta P8ESTACK_HI+3,x
jmp push_fac1_as_result rts
.pend .pend
equal_f .proc equal_f .proc
@ -415,24 +376,24 @@ equal_f .proc
inx inx
inx inx
inx inx
lda c64.ESTACK_LO-3,x lda P8ESTACK_LO-3,x
cmp c64.ESTACK_LO,x cmp P8ESTACK_LO,x
bne _equals_false bne _equals_false
lda c64.ESTACK_LO-2,x lda P8ESTACK_LO-2,x
cmp c64.ESTACK_LO+1,x cmp P8ESTACK_LO+1,x
bne _equals_false bne _equals_false
lda c64.ESTACK_LO-1,x lda P8ESTACK_LO-1,x
cmp c64.ESTACK_LO+2,x cmp P8ESTACK_LO+2,x
bne _equals_false bne _equals_false
lda c64.ESTACK_HI-2,x lda P8ESTACK_HI-2,x
cmp c64.ESTACK_HI+1,x cmp P8ESTACK_HI+1,x
bne _equals_false bne _equals_false
lda c64.ESTACK_HI-1,x lda P8ESTACK_HI-1,x
cmp c64.ESTACK_HI+2,x cmp P8ESTACK_HI+2,x
bne _equals_false bne _equals_false
_equals_true lda #1 _equals_true lda #1
_equals_store inx _equals_store inx
sta c64.ESTACK_LO+1,x sta P8ESTACK_LO+1,x
rts rts
_equals_false lda #0 _equals_false lda #0
beq _equals_store beq _equals_store
@ -442,7 +403,7 @@ notequal_f .proc
; -- are the two mflpt5 numbers on the stack different? ; -- are the two mflpt5 numbers on the stack different?
jsr equal_f jsr equal_f
eor #1 ; invert the result eor #1 ; invert the result
sta c64.ESTACK_LO+1,x sta P8ESTACK_LO+1,x
rts rts
.pend .pend
@ -495,12 +456,12 @@ compare_floats .proc
jsr MOVFM ; fac1 = flt1 jsr MOVFM ; fac1 = flt1
lda #<fmath_float2 lda #<fmath_float2
ldy #>fmath_float2 ldy #>fmath_float2
stx c64.SCRATCH_ZPREG stx P8ZP_SCRATCH_REG
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2) jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
ldx c64.SCRATCH_ZPREG ldx P8ZP_SCRATCH_REG
rts rts
_return_false lda #0 _return_false lda #0
_return_result sta c64.ESTACK_LO,x _return_result sta P8ESTACK_LO,x
dex dex
rts rts
_return_true lda #1 _return_true lda #1
@ -510,7 +471,7 @@ _return_true lda #1
func_sin .proc func_sin .proc
; -- push sin(f) back onto stack ; -- push sin(f) back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr SIN jsr SIN
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -518,7 +479,7 @@ func_sin .proc
func_cos .proc func_cos .proc
; -- push cos(f) back onto stack ; -- push cos(f) back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr COS jsr COS
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -526,7 +487,7 @@ func_cos .proc
func_tan .proc func_tan .proc
; -- push tan(f) back onto stack ; -- push tan(f) back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr TAN jsr TAN
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -534,7 +495,7 @@ func_tan .proc
func_atan .proc func_atan .proc
; -- push atan(f) back onto stack ; -- push atan(f) back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr ATN jsr ATN
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -542,7 +503,7 @@ func_atan .proc
func_ln .proc func_ln .proc
; -- push ln(f) back onto stack ; -- push ln(f) back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr LOG jsr LOG
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -550,7 +511,7 @@ func_ln .proc
func_log2 .proc func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack ; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr LOG jsr LOG
jsr MOVEF jsr MOVEF
lda #<c64.FL_LOG2 lda #<c64.FL_LOG2
@ -562,7 +523,7 @@ func_log2 .proc
func_sqrt .proc func_sqrt .proc
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr SQR jsr SQR
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -570,7 +531,7 @@ func_sqrt .proc
func_rad .proc func_rad .proc
; -- convert degrees to radians (d * pi / 180) ; -- convert degrees to radians (d * pi / 180)
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
lda #<_pi_div_180 lda #<_pi_div_180
ldy #>_pi_div_180 ldy #>_pi_div_180
jsr FMULT jsr FMULT
@ -581,7 +542,7 @@ _pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
func_deg .proc func_deg .proc
; -- convert radians to degrees (d * (1/ pi * 180)) ; -- convert radians to degrees (d * (1/ pi * 180))
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
lda #<_one_over_pi_div_180 lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180 ldy #>_one_over_pi_div_180
jsr FMULT jsr FMULT
@ -591,7 +552,7 @@ _one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
func_round .proc func_round .proc
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr FADDH jsr FADDH
jsr INT jsr INT
jmp push_fac1_as_result jmp push_fac1_as_result
@ -599,7 +560,7 @@ func_round .proc
func_floor .proc func_floor .proc
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
jsr INT jsr INT
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -607,7 +568,7 @@ func_floor .proc
func_ceil .proc func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1 ; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx P8ZP_SCRATCH_REG
ldx #<fmath_float1 ldx #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr MOVMF jsr MOVMF
@ -617,53 +578,53 @@ func_ceil .proc
jsr FCOMP jsr FCOMP
cmp #0 cmp #0
beq + beq +
lda #<FL_FONE lda #<FL_ONE_const
ldy #>FL_FONE ldy #>FL_ONE_const
jsr FADD jsr FADD
+ jmp push_fac1_as_result + jmp push_fac1_as_result
.pend .pend
func_any_f .proc func_any_f .proc
inx inx
lda c64.ESTACK_LO,x ; array size lda P8ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1 sta P8ZP_SCRATCH_B1
asl a asl a
asl a asl a
clc clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float adc P8ZP_SCRATCH_B1 ; times 5 because of float
jmp prog8_lib.func_any_b._entry jmp prog8_lib.func_any_b._entry
.pend .pend
func_all_f .proc func_all_f .proc
inx inx
jsr prog8_lib.peek_address jsr prog8_lib.peek_address
lda c64.ESTACK_LO,x ; array size lda P8ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1 sta P8ZP_SCRATCH_B1
asl a asl a
asl a asl a
clc clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float adc P8ZP_SCRATCH_B1 ; times 5 because of float
tay tay
dey dey
- lda (c64.SCRATCH_ZPWORD1),y - lda (P8ZP_SCRATCH_W1),y
clc clc
dey dey
adc (c64.SCRATCH_ZPWORD1),y adc (P8ZP_SCRATCH_W1),y
dey dey
adc (c64.SCRATCH_ZPWORD1),y adc (P8ZP_SCRATCH_W1),y
dey dey
adc (c64.SCRATCH_ZPWORD1),y adc (P8ZP_SCRATCH_W1),y
dey dey
adc (c64.SCRATCH_ZPWORD1),y adc (P8ZP_SCRATCH_W1),y
dey dey
cmp #0 cmp #0
beq + beq +
cpy #255 cpy #255
bne - bne -
lda #1 lda #1
sta c64.ESTACK_LO+1,x sta P8ESTACK_LO+1,x
rts rts
+ sta c64.ESTACK_LO+1,x + sta P8ESTACK_LO+1,x
rts rts
.pend .pend
@ -674,26 +635,28 @@ func_max_f .proc
ldy #>_largest_neg_float ldy #>_largest_neg_float
_minmax_entry jsr MOVFM _minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX stx floats_store_reg
- sty c64.SCRATCH_ZPREG - sty P8ZP_SCRATCH_REG
lda c64.SCRATCH_ZPWORD1 lda P8ZP_SCRATCH_W1
ldy c64.SCRATCH_ZPWORD1+1 ldy P8ZP_SCRATCH_W1+1
jsr FCOMP jsr FCOMP
_minmax_cmp cmp #255 ; modified _minmax_cmp cmp #255 ; modified
bne + bne +
lda c64.SCRATCH_ZPWORD1 lda P8ZP_SCRATCH_W1
ldy c64.SCRATCH_ZPWORD1+1 ldy P8ZP_SCRATCH_W1+1
jsr MOVFM jsr MOVFM
+ lda c64.SCRATCH_ZPWORD1 + lda P8ZP_SCRATCH_W1
clc clc
adc #5 adc #5
sta c64.SCRATCH_ZPWORD1 sta P8ZP_SCRATCH_W1
bcc + bcc +
inc c64.SCRATCH_ZPWORD1+1 inc P8ZP_SCRATCH_W1+1
+ ldy c64.SCRATCH_ZPREG + ldy P8ZP_SCRATCH_REG
dey dey
cpy #255 cpy #255
bne - bne -
ldx floats_store_reg
stx P8ZP_SCRATCH_REG
jmp push_fac1_as_result jmp push_fac1_as_result
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38 _largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend .pend
@ -709,33 +672,35 @@ _largest_pos_float .byte 255,127,255,255,255 ; largest positive float
.pend .pend
func_sum_f .proc func_sum_f .proc
lda #<FL_ZERO lda #<FL_ZERO_const
ldy #>FL_ZERO ldy #>FL_ZERO_const
jsr MOVFM jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX stx floats_store_reg
- sty c64.SCRATCH_ZPREG - sty P8ZP_SCRATCH_REG
lda c64.SCRATCH_ZPWORD1 lda P8ZP_SCRATCH_W1
ldy c64.SCRATCH_ZPWORD1+1 ldy P8ZP_SCRATCH_W1+1
jsr FADD jsr FADD
ldy c64.SCRATCH_ZPREG ldy P8ZP_SCRATCH_REG
dey dey
cpy #255 cpy #255
beq + beq +
lda c64.SCRATCH_ZPWORD1 lda P8ZP_SCRATCH_W1
clc clc
adc #5 adc #5
sta c64.SCRATCH_ZPWORD1 sta P8ZP_SCRATCH_W1
bcc - bcc -
inc c64.SCRATCH_ZPWORD1+1 inc P8ZP_SCRATCH_W1+1
bne - bne -
+ jmp push_fac1_as_result + ldx floats_store_reg
stx P8ZP_SCRATCH_REG
jmp push_fac1_as_result
.pend .pend
sign_f .proc sign_f .proc
jsr pop_float_fac1 jsr pop_float_fac1
jsr SIGN jsr SIGN
sta c64.ESTACK_LO,x sta P8ESTACK_LO,x
dex dex
rts rts
.pend .pend
@ -744,22 +709,22 @@ sign_f .proc
set_0_array_float .proc set_0_array_float .proc
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1) ; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
inx inx
lda c64.ESTACK_LO,x lda P8ESTACK_LO,x
asl a asl a
asl a asl a
clc clc
adc c64.ESTACK_LO,x adc P8ESTACK_LO,x
tay tay
lda #0 lda #0
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
iny iny
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
iny iny
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
iny iny
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
iny iny
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
rts rts
.pend .pend
@ -767,14 +732,13 @@ set_0_array_float .proc
set_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) ; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
inx inx
lda c64.ESTACK_LO,x lda P8ESTACK_LO,x
asl a asl a
asl a asl a
clc clc
adc c64.ESTACK_LO,x adc P8ESTACK_LO,x
clc adc P8ZP_SCRATCH_W2
adc c64.SCRATCH_ZPWORD2 ldy P8ZP_SCRATCH_W2+1
ldy c64.SCRATCH_ZPWORD2+1
bcc + bcc +
iny iny
+ jmp copy_float + jmp copy_float
@ -786,12 +750,12 @@ set_array_float .proc
swap_floats .proc swap_floats .proc
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2 ; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
ldy #4 ldy #4
- lda (c64.SCRATCH_ZPWORD1),y - lda (P8ZP_SCRATCH_W1),y
pha pha
lda (c64.SCRATCH_ZPWORD2),y lda (P8ZP_SCRATCH_W2),y
sta (c64.SCRATCH_ZPWORD1),y sta (P8ZP_SCRATCH_W1),y
pla pla
sta (c64.SCRATCH_ZPWORD2),y sta (P8ZP_SCRATCH_W2),y
dey dey
bpl - bpl -
rts rts

View File

@ -4,14 +4,14 @@
; ;
; indent format: TABS, size=8 ; indent format: TABS, size=8
%target c64
%option enable_floats %option enable_floats
floats {
c64flt {
; ---- this block contains C-64 floating point related functions ---- ; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793 const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586 const float TWOPI = 6.283185307179586
; ---- C64 basic and kernal ROM float constants and functions ---- ; ---- C64 basic and kernal ROM float constants and functions ----
@ -35,13 +35,11 @@ c64flt {
&float FL_TWOPI = $e2e5 ; 2 * PI &float FL_TWOPI = $e2e5 ; 2 * PI
&float FL_FR4 = $e2ea ; .25 &float FL_FR4 = $e2ea ; .25
; oddly enough, 0.0 isn't available in the kernel. ; oddly enough, 0.0 isn't available in the kernel.
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
; note: fac1/2 might get clobbered even if not mentioned in the function's name. ; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1. ; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
; checked functions below:
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1 romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1 romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2 romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
@ -52,22 +50,22 @@ romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt 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) ; 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. 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) ; 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 $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 $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) 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 ; 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) ; (tip: use floats.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 floats.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 floats.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 floats.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) ; 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 $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 romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
@ -91,6 +89,7 @@ romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2) romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1 romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1) romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
@ -163,9 +162,9 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) { asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1 ; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{ %asm {{
sta c64.SCRATCH_ZPREG sta P8ZP_SCRATCH_REG
tya tya
ldy c64.SCRATCH_ZPREG ldy P8ZP_SCRATCH_REG
jmp GIVAYF ; this uses the inverse order, Y/A jmp GIVAYF ; this uses the inverse order, Y/A
}} }}
} }
@ -174,9 +173,9 @@ asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y ; ---- fac1 to signed word in A/Y
%asm {{ %asm {{
jsr FTOSWORDYA ; note the inverse Y/A order jsr FTOSWORDYA ; note the inverse Y/A order
sta c64.SCRATCH_ZPREG sta P8ZP_SCRATCH_REG
tya tya
ldy c64.SCRATCH_ZPREG ldy P8ZP_SCRATCH_REG
rts rts
}} }}
} }
@ -185,41 +184,34 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y ; ---- fac1 to unsigned word in A/Y
%asm {{ %asm {{
jsr GETADR ; this uses the inverse order, Y/A jsr GETADR ; this uses the inverse order, Y/A
sta c64.SCRATCH_ZPB1 sta P8ZP_SCRATCH_B1
tya tya
ldy c64.SCRATCH_ZPB1 ldy P8ZP_SCRATCH_B1
rts rts
}} }}
} }
sub print_f (float value) { sub print_f (float value) {
; ---- prints the floating point value (without a newline) using basic rom routines. ; ---- prints the floating point value (without a newline).
%asm {{ %asm {{
stx c64.SCRATCH_ZPREGX stx floats_store_reg
lda #<value lda #<value
ldy #>value ldy #>value
jsr MOVFM ; load float into fac1 jsr MOVFM ; load float into fac1
jsr FOUT ; fac1 to string in A/Y jsr FOUT ; fac1 to string in A/Y
jsr c64.STROUT ; print string in A/Y sta P8ZP_SCRATCH_W1
ldx c64.SCRATCH_ZPREGX sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
jsr c64.CHROUT
iny
bne -
+ ldx floats_store_reg
rts rts
}} }}
} }
sub print_fln (float value) { %asminclude "library:c64/floats.asm", ""
; ---- prints the floating point value (with a newline at the end) using basic rom routines
%asm {{
stx c64.SCRATCH_ZPREGX
lda #<value
ldy #>value
jsr MOVFM ; load float into fac1
jsr FPRINTLN ; print fac1 with newline
ldx c64.SCRATCH_ZPREGX
rts
}}
} }
%asminclude "library:c64floats.asm", ""
} ; ------ end of block c64flt

View File

@ -0,0 +1,244 @@
%target c64
%import textio
; bitmap pixel graphics module for the C64
; only black/white monchrome 320x200 for now
; assumes bitmap screen memory is $2000-$3fff
graphics {
const uword BITMAP_ADDRESS = $2000
const uword WIDTH = 320
const ubyte HEIGHT = 200
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
c64.SCROLY |= %00100000
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
clear_screen(1, 0)
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
memset(BITMAP_ADDRESS, 320*200/8, 0)
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; Bresenham algorithm.
; This code special cases various quadrant loops to allow simple ++ and -- operations.
; TODO rewrite this in optimized assembly
if y1>y2 {
; make sure dy is always positive to avoid 8 instead of just 4 special cases
swap(x1, x2)
swap(y1, y2)
}
word @zp d = 0
ubyte positive_ix = true
word @zp dx = x2-x1
word @zp dy = y2-y1
if dx < 0 {
dx = -dx
positive_ix = false
}
dx *= 2
dy *= 2
internal_plotx = x1
if dx >= dy {
if positive_ix {
repeat {
internal_plot(y1)
if internal_plotx==x2
return
internal_plotx++
d += dy
if d > dx {
y1++
d -= dx
}
}
} else {
repeat {
internal_plot(y1)
if internal_plotx==x2
return
internal_plotx--
d += dy
if d > dx {
y1++
d -= dx
}
}
}
}
else {
if positive_ix {
repeat {
internal_plot(y1)
if y1 == y2
return
y1++
d += dx
if d > dy {
internal_plotx++
d -= dy
}
}
} else {
repeat {
internal_plot(y1)
if y1 == y2
return
y1++
d += dx
if d > dy {
internal_plotx--
d -= dy
}
}
}
}
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm
ubyte @zp ploty
ubyte @zp xx = radius
ubyte @zp yy = 0
byte @zp decisionOver2 = 1-xx as byte
while xx>=yy {
internal_plotx = xcenter + xx
ploty = ycenter + yy
internal_plot(ploty)
internal_plotx = xcenter - xx
internal_plot(ploty)
internal_plotx = xcenter + xx
ploty = ycenter - yy
internal_plot(ploty)
internal_plotx = xcenter - xx
internal_plot(ploty)
internal_plotx = xcenter + yy
ploty = ycenter + xx
internal_plot(ploty)
internal_plotx = xcenter - yy
internal_plot(ploty)
internal_plotx = xcenter + yy
ploty = ycenter - xx
internal_plot(ploty)
internal_plotx = xcenter - yy
internal_plot(ploty)
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
}
}
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm, filled
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
while xx>=yy {
ubyte ycenter_plus_yy = ycenter + yy
ubyte ycenter_min_yy = ycenter - yy
ubyte ycenter_plus_xx = ycenter + xx
ubyte ycenter_min_xx = ycenter - xx
for internal_plotx in xcenter to xcenter+xx {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
}
for internal_plotx in xcenter-xx to xcenter-1 {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
}
for internal_plotx in xcenter to xcenter+yy {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_min_xx)
}
for internal_plotx in xcenter-yy to xcenter {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_min_xx)
}
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
}
}
}
; here is the non-asm code for the plot routine below:
; sub plot_nonasm(uword px, ubyte py) {
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
; uword addr = BITMAP_ADDRESS + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; @(addr) |= ormask[lsb(px) & 7]
; }
asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
%asm {{
stx internal_plotx
sty internal_plotx+1
jmp internal_plot
}}
}
; for efficiency of internal algorithms here is the internal plot routine
; that takes the plotx coordinate in a separate variable instead of the XY register pair:
uword internal_plotx ; 0..319 ; separate 'parameter' for internal_plot()
asmsub internal_plot(ubyte ploty @A) clobbers (A, X, Y) { ; internal_plotx is 16 bits 0 to 319... doesn't fit in a register
%asm {{
tay
lda internal_plotx+1
sta P8ZP_SCRATCH_W2+1
lsr a ; 0
sta P8ZP_SCRATCH_W2
lda internal_plotx
pha
and #7
tax
lda _y_lookup_lo,y
clc
adc P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W2
lda _y_lookup_hi,y
adc P8ZP_SCRATCH_W2+1
sta P8ZP_SCRATCH_W2+1
pla ; internal_plotx
and #%11111000
tay
lda (P8ZP_SCRATCH_W2),y
ora _ormask,x
sta (P8ZP_SCRATCH_W2),y
rts
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; the y lookup tables encodes this formula: BITMAP_ADDRESS + 320*(py>>3) + (py & 7) (y from 0..199)
; We use the 64tass syntax for range expressions to calculate this table on assembly time.
_plot_y_values := $2000 + 320*(range(200)>>3) + (range(200) & 7)
_y_lookup_lo .byte <_plot_y_values
_y_lookup_hi .byte >_plot_y_values
}}
}
}

View File

@ -5,20 +5,13 @@
; ;
; indent format: TABS, size=8 ; indent format: TABS, size=8
%target c64
c64 { c64 {
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte &ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte &ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec &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 STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ) &ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
@ -183,19 +176,11 @@ c64 {
; ---- end of SID registers ---- ; ---- end of SID registers ----
; ---- C64 ROM kernal routines ----
; ---- C64 basic routines ----
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
; ---- end of C64 basic routines ----
; ---- C64 kernal routines ----
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead) romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
@ -238,6 +223,226 @@ romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X. romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- end of C64 kernal routines ---- ; ---- end of C64 ROM kernal routines ----
; ---- C64 specific system utility routines: ----
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
; This means that the BASIC, KERNAL and CHARGEN ROMs are banked in,
; the VIC, SID and CIA chips are reset, screen is cleared, and the default IRQ is set.
; Also a different color scheme is chosen to identify ourselves a little.
; Uppercase charset is activated, and all three registers set to 0, status flags cleared.
%asm {{
sei
cld
lda #%00101111
sta $00
lda #%00100111
sta $01
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
lda #6
sta c64.EXTCOL
lda #7
sta c64.COLOR
lda #0
sta c64.BGCOL0
jsr disable_runstop_and_charsetswitch
clc
clv
cli
rts
}}
}
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
lda #<_irq_handler
sta c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
asmsub set_irqvec() clobbers(A) {
%asm {{
sei
lda #<_irq_handler
sta c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr _irq_handler_init
jsr irq.irq
jsr _irq_handler_end
jmp c64.IRQDFRT ; continue with normal kernel irq routine
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
stx IRQ_X_REG
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
; stack protector; make sure we don't clobber the top of the evaluation stack
dex
dex
dex
dex
dex
dex
cld
rts
_irq_handler_end
; restore all zp scratch registers and the X register
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
ldx IRQ_X_REG
rts
IRQ_X_REG .byte 0
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irqvec() {
%asm {{
sei
lda #<c64.IRQDFRT
sta c64.CINV
lda #>c64.IRQDFRT
sta c64.CINV+1
lda #0
sta c64.IREQMASK ; disable raster irq
lda #%10000001
sta c64.CIA1ICR ; restore CIA1 irq
cli
rts
}}
}
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
%asm {{
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFRT
_setup_raster_irq
pha
lda #%01111111
sta c64.CIA1ICR ; "switch off" interrupts signals from cia-1
sta c64.CIA2ICR ; "switch off" interrupts signals from cia-2
and c64.SCROLY
sta c64.SCROLY ; clear most significant bit of raster position
lda c64.CIA1ICR ; ack previous irq
lda c64.CIA2ICR ; ack previous irq
pla
sta c64.RASTER ; set the raster line number where interrupt should occur
cpy #0
beq +
lda c64.SCROLY
ora #%10000000
sta c64.SCROLY ; set most significant bit of raster position
+ lda #%00000001
sta c64.IREQMASK ;enable raster interrupt signals from vic
rts
}}
}
asmsub set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) {
%asm {{
sei
jsr set_rasterirq._setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
; ---- end of C64 specific system utility routines ----
} }

View File

@ -0,0 +1,587 @@
; Prog8 definitions for the Text I/O and Screen routines for the Commodore-64
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
%target c64
%import syslib
%import conv
txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
sub clear_screen() {
clear_screenchars(' ')
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)
%asm {{
pha
tya
jsr clear_screencolors
pla
jsr clear_screenchars
rts
}}
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
ldy #0
_loop sta c64.Screen,y
sta c64.Screen+$0100,y
sta c64.Screen+$0200,y
sta c64.Screen+$02e8,y
iny
bne _loop
rts
}}
}
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
; ---- clear the character screen colors with the given color (leaves characters).
; (assumes color matrix is at the default address)
%asm {{
ldy #0
_loop sta c64.Colors,y
sta c64.Colors+$0100,y
sta c64.Colors+$0200,y
sta c64.Colors+$02e8,y
iny
bne _loop
rts
}}
}
sub color (ubyte txtcol) {
c64.COLOR = txtcol
}
sub lowercase() {
c64.VMCSB |= 2
}
sub uppercase() {
c64.VMCSB &= ~2
}
asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
+ ; scroll the color memory
ldx #0
ldy #38
-
.for row=0, row<=24, row+=1
lda c64.Colors + 40*row + 1,x
sta c64.Colors + 40*row,x
.next
inx
dey
bpl -
_scroll_screen ; scroll the screen memory
ldx #0
ldy #38
-
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row,x
.next
inx
dey
bpl -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
+ ; scroll the color memory
ldx #38
-
.for row=0, row<=24, row+=1
lda c64.Colors + 40*row + 0,x
sta c64.Colors + 40*row + 1,x
.next
dex
bpl -
_scroll_screen ; scroll the screen memory
ldx #38
-
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 0,x
sta c64.Screen + 40*row + 1,x
.next
dex
bpl -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
+ ; scroll the color memory
ldx #39
-
.for row=1, row<=24, row+=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row-1),x
.next
dex
bpl -
_scroll_screen ; scroll the screen memory
ldx #39
-
.for row=1, row<=24, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
.next
dex
bpl -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx P8ZP_SCRATCH_REG
bcs +
jmp _scroll_screen
+ ; scroll the color memory
ldx #39
-
.for row=23, row>=0, row-=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row+1),x
.next
dex
bpl -
_scroll_screen ; scroll the screen memory
ldx #39
-
.for row=23, row>=0, row-=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
dex
bpl -
ldx P8ZP_SCRATCH_REG
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 {{
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
pha
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
txa
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr c64.CHROUT
_ones txa
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_b (byte value @ A) clobbers(A,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
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 {{
stx P8ZP_SCRATCH_REG
bcc +
pha
lda #'$'
jsr c64.CHROUT
pla
+ jsr conv.ubyte2hex
jsr c64.CHROUT
tya
jsr c64.CHROUT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr c64.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr c64.CHROUT
dey
bne -
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr c64.CHROUT
iny
bne -
+ ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
stx P8ZP_SCRATCH_REG
jsr conv.uword2decimal
ldx P8ZP_SCRATCH_REG
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, Y) {
; ---- sets the character in the screen matrix at the given position
%asm {{
pha
tya
asl a
tay
lda _screenrows+1,y
sta _mod+2
txa
clc
adc _screenrows,y
sta _mod+1
bcc +
inc _mod+2
+ pla
_mod sta $ffff ; modified
rts
_screenrows .word $0400 + range(0, 1000, 40)
}}
}
asmsub getchr (ubyte col @A, ubyte row @Y) clobbers(Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
%asm {{
pha
tya
asl a
tay
lda setchr._screenrows+1,y
sta _mod+2
pla
clc
adc setchr._screenrows,y
sta _mod+1
bcc _mod
inc _mod+2
_mod lda $ffff ; modified
rts
}}
}
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 {{
pha
tya
asl a
tay
lda _colorrows+1,y
sta _mod+2
txa
clc
adc _colorrows,y
sta _mod+1
bcc +
inc _mod+2
+ pla
_mod sta $ffff ; modified
rts
_colorrows .word $d800 + range(0, 1000, 40)
}}
}
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 {{
pha
tya
asl a
tay
lda setclr._colorrows+1,y
sta _mod+2
pla
clc
adc setclr._colorrows,y
sta _mod+1
bcc _mod
inc _mod+2
_mod lda $ffff ; modified
rts
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
lda row
asl a
tay
lda setchr._screenrows+1,y
sta _charmod+2
adc #$d4
sta _colormod+2
lda setchr._screenrows,y
clc
adc column
sta _charmod+1
sta _colormod+1
bcc +
inc _charmod+2
inc _colormod+2
+ lda char
_charmod sta $ffff ; modified
lda charcolor
_colormod sta $ffff ; modified
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
%asm {{
stx P8ZP_SCRATCH_REG
tax
clc
jsr c64.PLOT
ldx P8ZP_SCRATCH_REG
rts
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
; -- returns the text screen width (number of columns)
%asm {{
jsr c64.SCREEN
txa
rts
}}
}
asmsub height() clobbers(X, Y) -> ubyte @A {
; -- returns the text screen height (number of rows)
%asm {{
jsr c64.SCREEN
tya
rts
}}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,361 @@
; Prog8 definitions for number conversions routines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
conv {
; ----- number conversions to decimal strings
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
rts
}}
}
asmsub uword2decimal (uword value @ AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
%asm {{
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
;HexToDec99
; start in A
; end with A = 10's, decOnes (also in X)
;HexToDec255
; start in A
; end with Y = 100's, A = 10's, decOnes (also in X)
;HexToDec999
; start with A = high byte, Y = low byte
; end with Y = 100's, A = 10's, decOnes (also in X)
; requires 1 extra temp register on top of decOnes, could combine
; these two if HexToDec65535 was eliminated...
;HexToDec65535
; start with A/Y (low/high) as 16 bit value
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
ASCII_0_OFFSET = $30
temp = P8ZP_SCRATCH_B1 ; byte in zeropage
hexHigh = P8ZP_SCRATCH_W1 ; byte in zeropage
hexLow = P8ZP_SCRATCH_W1+1 ; byte in zeropage
HexToDec65535; SUBROUTINE
sty hexHigh ;3 @9
sta hexLow ;3 @12
tya
tax ;2 @14
lsr a ;2 @16
lsr a ;2 @18 integer divide 1024 (result 0-63)
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
;at this point we have a number 1-65 that we have to times by 24,
;add to original sum, and Mod 1024 to get a remainder 0-999
sta temp ;3 @25
asl a ;2 @27
adc temp ;3 @30 x3
tay ;2 @32
lsr a ;2 @34
lsr a ;2 @36
lsr a ;2 @38
lsr a ;2 @40
lsr a ;2 @42
tax ;2 @44
tya ;2 @46
asl a ;2 @48
asl a ;2 @50
asl a ;2 @52
clc ;2 @54
adc hexLow ;3 @57
sta hexLow ;3 @60
txa ;2 @62
adc hexHigh ;3 @65
sta hexHigh ;3 @68
ror a ;2 @70
lsr a ;2 @72
tay ;2 @74 integer divide 1,000 (result 0-65)
lsr a ;2 @76 split the 1,000 and 10,000 digit
tax ;2 @78
lda ShiftedBcdTab,x ;4 @82
tax ;2 @84
rol a ;2 @86
and #$0F ;2 @88
ora #ASCII_0_OFFSET
sta decThousands ;3 @91
txa ;2 @93
lsr a ;2 @95
lsr a ;2 @97
lsr a ;2 @99
ora #ASCII_0_OFFSET
sta decTenThousands ;3 @102
lda hexLow ;3 @105
cpy temp ;3 @108
bmi _doSubtract ;2³ @110/111
beq _useZero ;2³ @112/113
adc #23 + 24 ;2 @114
_doSubtract
sbc #23 ;2 @116
sta hexLow ;3 @119
_useZero
lda hexHigh ;3 @122
sbc #0 ;2 @124
Start100s
and #$03 ;2 @126
tax ;2 @128 0,1,2,3
cmp #2 ;2 @130
rol a ;2 @132 0,2,5,7
ora #ASCII_0_OFFSET
tay ;2 @134 Y = Hundreds digit
lda hexLow ;3 @137
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
bcs hex_doSub200 ;2³ @143/144
hex_try200
cmp #200 ;2 @145
bcc hex_try100 ;2³ @147/148
hex_doSub200
iny ;2 @149
iny ;2 @151
sbc #200 ;2 @153
hex_try100
cmp #100 ;2 @155
bcc HexToDec99 ;2³ @157/158
iny ;2 @159
sbc #100 ;2 @161
HexToDec99; SUBROUTINE
lsr a ;2 @163
tax ;2 @165
lda ShiftedBcdTab,x ;4 @169
tax ;2 @171
rol a ;2 @173
and #$0F ;2 @175
ora #ASCII_0_OFFSET
sta decOnes ;3 @178
txa ;2 @180
lsr a ;2 @182
lsr a ;2 @184
lsr a ;2 @186
ora #ASCII_0_OFFSET
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
sty decHundreds
sta decTens
ldx decOnes
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
HexToDec999; SUBROUTINE
sty hexLow ;3 @9
jmp Start100s ;3 @12
Mod100Tab
.byte 0,56,12,56+12
ShiftedBcdTab
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
decTenThousands .byte 0
decThousands .byte 0
decHundreds .byte 0
decTens .byte 0
decOnes .byte 0
.byte 0 ; zero-terminate the decimal output string
}}
}
; ----- utility functions ----
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{
cmp #0
bpl +
eor #255
clc
adc #1
+ jmp ubyte2decimal
}}
}
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
%asm {{
stx P8ZP_SCRATCH_REG
pha
and #$0f
tax
ldy _hex_digits,x
pla
lsr a
lsr a
lsr a
lsr a
tax
lda _hex_digits,x
ldx P8ZP_SCRATCH_REG
rts
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
}}
}
asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{
sta P8ZP_SCRATCH_REG
tya
jsr ubyte2hex
sta output
sty output+1
lda P8ZP_SCRATCH_REG
jsr ubyte2hex
sta output+2
sty output+3
rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
_result = P8ZP_SCRATCH_W2
sta _mod+1
sty _mod+2
ldy #0
sty _result
sty _result+1
_mod lda $ffff,y ; modified
sec
sbc #48
bpl +
_done ; return result
lda _result
ldy _result+1
rts
+ cmp #10
bcs _done
; add digit to result
pha
jsr _result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _mod
; never reached
_result_times_10 ; (W*4 + W)*2
lda _result+1
sta P8ZP_SCRATCH_REG
lda _result
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
clc
adc _result
sta _result
lda P8ZP_SCRATCH_REG
adc _result+1
asl _result
rol a
sta _result+1
rts
}}
}
asmsub str2word(str string @ AY) -> word @ AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
_result = P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
sty _result
sty _result+1
sty _negative
lda (P8ZP_SCRATCH_W1),y
cmp #'+'
bne +
iny
+ cmp #'-'
bne _parse
inc _negative
iny
_parse lda (P8ZP_SCRATCH_W1),y
sec
sbc #48
bpl _digit
_done ; return result
lda _negative
beq +
sec
lda #0
sbc _result
sta _result
lda #0
sbc _result+1
sta _result+1
+ lda _result
ldy _result+1
rts
_digit cmp #10
bcs _done
; add digit to result
pha
jsr str2uword._result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _parse
; never reached
_negative .byte 0
}}
}
}

View File

@ -0,0 +1,153 @@
; Prog8 definitions for floating point handling on the CommanderX16
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
%target cx16
%option enable_floats
floats {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
; ---- ROM float functions ----
; note: the fac1 and fac2 are working registers and take 6 bytes each,
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; (tip: use GIVAYFAY to use A/Y input; lo/hi switched to normal order)
romsub $fe03 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $fe06 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
romsub $fe09 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $fe0c = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
romsub $fe0f = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
romsub $fe12 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
romsub $fe15 = FADDT() clobbers(A,X,Y) ; fac1 += fac2
romsub $fe1b = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
romsub $fe1e = NORMAL() clobbers(A,X,Y) ; normalize fac1 (?)
romsub $fe24 = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $fe27 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
romsub $fe2a = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $fe33 = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
romsub $fe36 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $fe3c = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $fe3f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $fe42 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $fe48 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
romsub $fe4b = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
romsub $fe4e = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe51 = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $fe54 = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $fe5a = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe5d = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $fe60 = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $fe6c = ABS() ; fac1 = ABS(fac1)
romsub $fe6f = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $fe78 = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
romsub $fea2 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $fea5 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $fea8 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $feab = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $feae = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
phx
sta P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_REG
jsr GIVAYF ; load it as signed... correct afterwards
lda P8ZP_SCRATCH_B1
bpl +
lda #<_flt65536
ldy #>_flt65536
jsr FADD
+ plx
rts
_flt65536 .byte 145,0,0,0,0 ; 65536.0
}}
}
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{
sta P8ZP_SCRATCH_REG
tya
ldy P8ZP_SCRATCH_REG
jmp GIVAYF ; this uses the inverse order, Y/A
}}
}
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y
%asm {{
jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_REG
tya
ldy P8ZP_SCRATCH_REG
rts
}}
}
asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y
%asm {{
jsr GETADR ; this uses the inverse order, Y/A
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_B1
rts
}}
}
sub print_f (float value) {
; ---- prints the floating point value (without a newline).
%asm {{
phx
lda #<value
ldy #>value
jsr MOVFM ; load float into fac1
jsr FOUT ; fac1 to string in A/Y
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
jsr c64.CHROUT
iny
bne -
+ plx
rts
}}
}
%asminclude "library:c64/floats.asm", ""
}

View File

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

View File

@ -0,0 +1,291 @@
; Prog8 definitions for the CommanderX16
; Including memory registers, I/O registers, Basic and Kernal subroutines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
%target cx16
c64 {
; ---- kernal routines, these are the same as on the Commodore-64 (hence the same block name) ----
; STROUT --> use screen.print
; CLEARSCR -> use screen.clear_screen
; HOMECRSR -> use screen.plot
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> ubyte @A, uword @ XY ; read/set top of memory pointer, returns number of banks in A
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use screen.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
}
cx16 {
; 65c02 hardware vectors:
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; the sixteen virtual 16-bit registers
&uword r0 = $0002
&uword r1 = $0004
&uword r2 = $0006
&uword r3 = $0008
&uword r4 = $000a
&uword r5 = $000c
&uword r6 = $000e
&uword r7 = $0010
&uword r8 = $0012
&uword r9 = $0014
&uword r10 = $0016
&uword r11 = $0018
&uword r12 = $001a
&uword r13 = $001c
&uword r14 = $001e
&uword r15 = $0020
; VERA registers
const uword VERA_BASE = $9F20
&ubyte VERA_ADDR_L = VERA_BASE + $0000
&ubyte VERA_ADDR_M = VERA_BASE + $0001
&ubyte VERA_ADDR_H = VERA_BASE + $0002
&ubyte VERA_DATA0 = VERA_BASE + $0003
&ubyte VERA_DATA1 = VERA_BASE + $0004
&ubyte VERA_CTRL = VERA_BASE + $0005
&ubyte VERA_IEN = VERA_BASE + $0006
&ubyte VERA_ISR = VERA_BASE + $0007
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
; VERA_PSG_BASE = $1F9C0
; VERA_PALETTE_BASE = $1FA00
; VERA_SPRITES_BASE = $1FC00
; I/O
const uword via1 = $9f60 ;VIA 6522 #1
&ubyte d1prb = via1+0
&ubyte d1pra = via1+1
&ubyte d1ddrb = via1+2
&ubyte d1ddra = via1+3
&ubyte d1t1l = via1+4
&ubyte d1t1h = via1+5
&ubyte d1t1ll = via1+6
&ubyte d1t1lh = via1+7
&ubyte d1t2l = via1+8
&ubyte d1t2h = via1+9
&ubyte d1sr = via1+10
&ubyte d1acr = via1+11
&ubyte d1pcr = via1+12
&ubyte d1ifr = via1+13
&ubyte d1ier = via1+14
&ubyte d1ora = via1+15
const uword via2 = $9f70 ;VIA 6522 #2
&ubyte d2prb =via2+0
&ubyte d2pra =via2+1
&ubyte d2ddrb =via2+2
&ubyte d2ddra =via2+3
&ubyte d2t1l =via2+4
&ubyte d2t1h =via2+5
&ubyte d2t1ll =via2+6
&ubyte d2t1lh =via2+7
&ubyte d2t2l =via2+8
&ubyte d2t2h =via2+9
&ubyte d2sr =via2+10
&ubyte d2acr =via2+11
&ubyte d2pcr =via2+12
&ubyte d2ifr =via2+13
&ubyte d2ier =via2+14
&ubyte d2ora =via2+15
; ---- Commander X-16 additions on top of C64 kernal routines ----
; spelling of the names is taken from the Commander X-16 rom sources
; supported C128 additions
romsub $ff4a = close_all(ubyte device @A) clobbers(A,X,Y)
romsub $ff59 = lkupla(ubyte la @A) clobbers(A,X,Y)
romsub $ff5c = lkupsa(ubyte sa @Y) clobbers(A,X,Y)
romsub $ff5f = screen_set_mode(ubyte mode @A) clobbers(A, X, Y) -> ubyte @Pc
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
romsub $ff6e = jsrfar()
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7a = cmpare(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7d = primm()
; X16 additions
romsub $ff44 = macptr() clobbers(A,X,Y)
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
romsub $ff68 = mouse_config(ubyte shape @A, ubyte scale @X) clobbers (A, X, Y)
romsub $ff6b = mouse_get(ubyte zpdataptr @X) clobbers(A)
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff4d = clock_set_date_time() clobbers(A, X, Y) ; args: r0, r1, r2, r3L
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) ; outout args: r0, r1, r2, r3L
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
; high level graphics & fonts
romsub $ff20 = GRAPH_init() clobbers(A,X,Y) ; uses vectors=r0
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
romsub $ff26 = GRAPH_set_window() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
romsub $ff2c = GRAPH_draw_line() clobbers(A,X,Y) ; uses x1=r0, y1=r1, x2=r2, y2=r3
romsub $ff2f = GRAPH_draw_rect(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3, cornerradius=r4
romsub $ff32 = GRAPH_move_rect() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, width=r4, height=r5
romsub $ff35 = GRAPH_draw_oval(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $ff38 = GRAPH_draw_image() clobbers(A,X,Y) ; uses x=r0, y=r1, ptr=r2, width=r3, height=r4
romsub $ff3b = GRAPH_set_font() clobbers(A,X,Y) ; uses ptr=r0
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc) clobbers(A,X,Y)
romsub $ff41 = GRAPH_put_char(ubyte char @A) clobbers(A,X,Y) ; uses x=r0, y=r1
; framebuffer
romsub $fef6 = FB_init() clobbers(A,X,Y)
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A ; also outputs width=r0, height=r1
romsub $fefc = FB_set_palette(ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y) ; also uses pointer=r0
romsub $feff = FB_cursor_position() clobbers(A,X,Y) ; uses x=r0, y=r1
romsub $ff02 = FB_cursor_next_line() clobbers(A,X,Y) ; uses x=r0
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
romsub $ff08 = FB_get_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
romsub $ff0e = FB_set_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y) ; also uses mask=r0L
romsub $ff17 = FB_fill_pixels(ubyte color @A) clobbers(A,X,Y) ; also uses count=r0, step=r1
romsub $ff1a = FB_filter_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
romsub $ff1d = FB_move_pixels() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, count=r4
; misc
romsub $fef0 = sprite_set_image(ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc ; also uses pixels=r0, mask=r1, bpp=r2L
romsub $fef3 = sprite_set_position(ubyte number @A) clobbers(A,X,Y) ; also uses x=r0 and y=r1
romsub $fee4 = memory_fill(ubyte value @A) clobbers(A,X,Y) ; uses address=r0, num_bytes=r1
romsub $fee7 = memory_copy() clobbers(A,X,Y) ; uses source=r0, target=r1, num_bytes=r2
romsub $feea = memory_crc() clobbers(A,X,Y) ; uses address=r0, num_bytes=r1 result->r2
romsub $feed = memory_decompress() clobbers(A,X,Y) ; uses input=r0, output=r1 result->r1
romsub $fedb = console_init() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc) clobbers(A,X,Y)
romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
romsub $fed8 = console_put_image() clobbers(A,X,Y) ; uses ptr=r0, width=r1, height=r2
romsub $fed5 = console_set_paging_message() clobbers(A,X,Y) ; uses messageptr=r0
romsub $fed2 = kbdbuf_put(ubyte key @A) clobbers(A,X,Y)
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
romsub $fecc = monitor() clobbers(A,X,Y)
; ---- end of kernal routines ----
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
%asm {{
sei
cld
;stz $00
;stz $01
;stz d1prb ; select rom bank 0
lda #$80
sta VERA_CTRL
jsr c64.IOINIT
jsr c64.RESTOR
jsr c64.CINT
lda #$90 ; black
jsr c64.CHROUT
lda #1 ; swap fg/bg
jsr c64.CHROUT
lda #$9e ; yellow
jsr c64.CHROUT
lda #147 ; clear screen
jsr c64.CHROUT
lda #0
tax
tay
clc
clv
cli
rts
}}
}
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
%asm {{
sei
lda #14
sta $01
stz cx16.d1prb ; bank the kernal in
jmp (cx16.RESET_VEC)
}}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,6 @@
; ;
; indent format: TABS, size=8 ; indent format: TABS, size=8
%import c64lib
math { math {
%asminclude "library:math.asm", "" %asminclude "library:math.asm", ""
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1 +1 @@
2.3 4.4

View File

@ -4,12 +4,10 @@ import kotlinx.cli.*
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.compiler.CompilationResult import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.CompilationTarget import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
import java.io.IOException
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds import java.nio.file.StandardWatchEventKinds
@ -35,12 +33,13 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
private fun compileMain(args: Array<String>) { private fun compileMain(args: Array<String>) {
val cli = CommandLineInterface("prog8compiler") val cli = CommandLineInterface("prog8compiler")
val startEmulator by cli.flagArgument("-emu", "auto-start the Vice C-64 emulator after successful compilation") val startEmulator by cli.flagArgument("-emu", "auto-start emulator after successful compilation")
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".") val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code") val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations") 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 watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently only 'c64' (C64 6502 assembly) available", "c64") val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1) val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
try { try {
@ -49,26 +48,6 @@ private fun compileMain(args: Array<String>) {
exitProcess(1) exitProcess(1)
} }
when(compilationTarget) {
"c64" -> {
with(CompilationTarget) {
name = "c64"
machine = C64MachineDefinition
encodeString = { str, altEncoding ->
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
}
decodeString = { bytes, altEncoding ->
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
}
asmGenerator = ::AsmGen
}
}
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
val outputPath = pathFrom(outputDir) val outputPath = pathFrom(outputDir)
if(!outputPath.toFile().isDirectory) { if(!outputPath.toFile().isDirectory) {
System.err.println("Output path doesn't exist") System.err.println("Output path doesn't exist")
@ -83,7 +62,7 @@ private fun compileMain(args: Array<String>) {
println("Continuous watch mode active. Main module: $filepath") println("Continuous watch mode active. Main module: $filepath")
try { try {
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath) val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
println("Imported files (now watching:)") println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) { for (importedFile in compilationResult.importedFiles) {
print(" ") print(" ")
@ -108,7 +87,7 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize() val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult val compilationResult: CompilationResult
try { try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath) compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, compilationTarget, outputPath)
if(!compilationResult.success) if(!compilationResult.success)
exitProcess(1) exitProcess(1)
} catch (x: ParsingFailedError) { } catch (x: ParsingFailedError) {
@ -121,20 +100,7 @@ private fun compileMain(args: Array<String>) {
if (compilationResult.programName.isEmpty()) if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.") println("\nCan't start emulator because no program was assembled.")
else if(startEmulator) { else if(startEmulator) {
for(emulator in listOf("x64sc", "x64")) { CompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(x: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
} }
} }
} }

View File

@ -53,12 +53,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
} }
override fun visit(expr: BinaryExpression) { override fun visit(expr: BinaryExpression) {
output("(")
expr.left.accept(this) expr.left.accept(this)
if(expr.operator.any { it.isLetter() }) if(expr.operator.any { it.isLetter() })
output(" ${expr.operator} ") output(" ${expr.operator} ")
else else
output(expr.operator) output(expr.operator)
expr.right.accept(this) expr.right.accept(this)
output(")")
} }
override fun visit(directive: Directive) { 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_W -> "word["
DataType.ARRAY_F -> "float[" DataType.ARRAY_F -> "float["
DataType.STRUCT -> "" // the name of the struct is enough DataType.STRUCT -> "" // the name of the struct is enough
else -> "?????2" else -> "?????"
} }
} }
@ -113,7 +115,10 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
VarDeclType.CONST -> output("const ") VarDeclType.CONST -> output("const ")
VarDeclType.MEMORY -> output("&") VarDeclType.MEMORY -> output("&")
} }
output(decl.struct?.name ?: "")
if(decl.datatype==DataType.STRUCT && decl.struct!=null)
output(decl.struct!!.name)
output(datatypeString(decl.datatype)) output(datatypeString(decl.datatype))
if(decl.arraysize!=null) { if(decl.arraysize!=null) {
decl.arraysize!!.index.accept(this) decl.arraysize!!.index.accept(this)
@ -140,7 +145,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
param.second.stack -> "stack" param.second.stack -> "stack"
param.second.registerOrPair!=null -> param.second.registerOrPair.toString() param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
param.second.statusflag!=null -> param.second.statusflag.toString() param.second.statusflag!=null -> param.second.statusflag.toString()
else -> "?????1" else -> "?????"
} }
output("${datatypeString(param.first.type)} ${param.first.name} @$reg") output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
if(param.first!==subroutine.parameters.last()) if(param.first!==subroutine.parameters.last())
@ -287,12 +292,19 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
} }
override fun visit(assignment: Assignment) { override fun visit(assignment: Assignment) {
assignment.target.accept(this) val binExpr = assignment.value as? BinaryExpression
if (assignment.aug_op != null && assignment.aug_op != "setvalue") if(binExpr!=null && binExpr.left isSameAs assignment.target
output(" ${assignment.aug_op} ") && binExpr.operator !in setOf("and", "or", "xor")
else && binExpr.operator !in comparisonOperators) {
// we only support the inplace assignments of the form A = A <operator> <value>
assignment.target.accept(this)
output(" ${binExpr.operator}= ")
binExpr.right.accept(this)
} else {
assignment.target.accept(this)
output(" = ") output(" = ")
assignment.value.accept(this) assignment.value.accept(this)
}
} }
override fun visit(postIncrDecr: PostIncrDecr) { override fun visit(postIncrDecr: PostIncrDecr) {
@ -300,20 +312,13 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(postIncrDecr.operator) output(postIncrDecr.operator)
} }
override fun visit(contStmt: Continue) {
output("continue")
}
override fun visit(breakStmt: Break) { override fun visit(breakStmt: Break) {
output("break") output("break")
} }
override fun visit(forLoop: ForLoop) { override fun visit(forLoop: ForLoop) {
output("for ") output("for ")
if(forLoop.loopRegister!=null) forLoop.loopVar.accept(this)
output(forLoop.loopRegister.toString())
else
forLoop.loopVar!!.accept(this)
output(" in ") output(" in ")
forLoop.iterable.accept(this) forLoop.iterable.accept(this)
output(" ") output(" ")
@ -327,16 +332,18 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
whileLoop.body.accept(this) whileLoop.body.accept(this)
} }
override fun visit(foreverLoop: ForeverLoop) {
output("forever ")
foreverLoop.body.accept(this)
}
override fun visit(repeatLoop: RepeatLoop) { override fun visit(repeatLoop: RepeatLoop) {
output("repeat ") output("repeat ")
repeatLoop.iterations?.accept(this)
output(" ")
repeatLoop.body.accept(this) repeatLoop.body.accept(this)
}
override fun visit(untilLoop: UntilLoop) {
output("do ")
untilLoop.body.accept(this)
output(" until ") output(" until ")
repeatLoop.untilCondition.accept(this) untilLoop.condition.accept(this)
} }
override fun visit(returnStmt: Return) { override fun visit(returnStmt: Return) {
@ -352,12 +359,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
} }
override fun visit(assignTarget: AssignTarget) { override fun visit(assignTarget: AssignTarget) {
if(assignTarget.register!=null) assignTarget.memoryAddress?.accept(this)
output(assignTarget.register.toString()) assignTarget.identifier?.accept(this)
else {
assignTarget.memoryAddress?.accept(this)
assignTarget.identifier?.accept(this)
}
assignTarget.arrayindexed?.accept(this) assignTarget.arrayindexed?.accept(this)
} }
@ -398,10 +401,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
outputlni("}}") outputlni("}}")
} }
override fun visit(registerExpr: RegisterExpr) {
output(registerExpr.register.toString())
}
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) { override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
output(builtinFunctionStatementPlaceholder.name) output(builtinFunctionStatementPlaceholder.name)
} }
@ -435,12 +434,4 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
whenChoice.statements.accept(this) whenChoice.statements.accept(this)
outputln("") outputln("")
} }
override fun visit(structLv: StructLiteralValue) {
outputListMembers(structLv.values.asSequence(), '{', '}')
}
override fun visit(nopStatement: NopStatement) {
output("; NOP @ ${nopStatement.position} $nopStatement")
}
} }

View File

@ -57,7 +57,7 @@ interface INameScope {
when(stmt) { when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here! // NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> if(stmt.body.name==name) return stmt.body is ForLoop -> if(stmt.body.name==name) return stmt.body
is RepeatLoop -> if(stmt.body.name==name) return stmt.body is UntilLoop -> if(stmt.body.name==name) return stmt.body
is WhileLoop -> if(stmt.body.name==name) return stmt.body is WhileLoop -> if(stmt.body.name==name) return stmt.body
is BranchStatement -> { is BranchStatement -> {
if(stmt.truepart.name==name) return stmt.truepart if(stmt.truepart.name==name) return stmt.truepart
@ -137,7 +137,15 @@ interface INameScope {
} }
return null return null
} else { } 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 var statementScope = localContext
while(statementScope !is ParentSentinel) { while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope() val localScope = statementScope.definingScope()
@ -175,8 +183,8 @@ interface INameScope {
find(it.truepart) find(it.truepart)
find(it.elsepart) find(it.elsepart)
} }
is UntilLoop -> find(it.body)
is RepeatLoop -> find(it.body) is RepeatLoop -> find(it.body)
is ForeverLoop -> find(it.body)
is WhileLoop -> find(it.body) is WhileLoop -> find(it.body)
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) } is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
else -> { /* do nothing */ } else -> { /* do nothing */ }
@ -187,6 +195,14 @@ interface INameScope {
find(this) find(this)
return result return result
} }
fun nextSibling(stmt: Statement): Statement? {
val nextIdx = statements.indexOfFirst { it===stmt } + 1
return if(nextIdx < statements.size)
statements[nextIdx]
else
null
}
} }
interface IAssignable { interface IAssignable {
@ -224,13 +240,13 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
modules.forEach { modules.forEach {
it.linkParents(this) it.linkParents(namespace)
} }
} }
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Module && replacement is Module) require(node is Module && replacement is Module)
val idx = modules.withIndex().find { it.value===node }!!.index val idx = modules.indexOfFirst { it===node }
modules[idx] = replacement modules[idx] = replacement
replacement.parent = this replacement.parent = this
} }
@ -257,7 +273,7 @@ class Module(override val name: String,
override fun definingScope(): INameScope = program.namespace override fun definingScope(): INameScope = program.namespace
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Statement && replacement is Statement) require(node is Statement && replacement is Statement)
val idx = statements.withIndex().find { it.value===node }!!.index val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this replacement.parent = this
} }
@ -304,11 +320,19 @@ 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. // lookup something from the module.
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) { return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine -> stmt is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt
null -> null null -> null
else -> throw SyntaxError("wrong identifier target for $scopedName: $stmt", stmt.position) else -> throw SyntaxError("invalid identifier target type", stmt.position)
} }
} }
} }

View File

@ -30,7 +30,7 @@ private fun ParserRuleContext.toPosition() : Position {
val customTokensource = this.start.tokenSource as? CustomLexer val customTokensource = this.start.tokenSource as? CustomLexer
val filename = val filename =
when { when {
customTokensource!=null -> customTokensource.modulePath.fileName.toString() customTokensource!=null -> customTokensource.modulePath.toString()
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@" start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
else -> File(start.inputStream.sourceName).name else -> File(start.inputStream.sourceName).name
} }
@ -161,14 +161,15 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
if(vardecl!=null) return vardecl if(vardecl!=null) return vardecl
assignment()?.let { assignment()?.let {
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition()) return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition())
} }
augassignment()?.let { augassignment()?.let {
return Assignment(it.assign_target().toAst(), // replace A += X with A = A + X
it.operator.text, val target = it.assign_target().toAst()
it.expression().toAst(), val oper = it.operator.text.substringBefore('=')
it.toPosition()) val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
} }
postincrdecr()?.let { postincrdecr()?.let {
@ -205,21 +206,18 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val forloop = forloop()?.toAst() val forloop = forloop()?.toAst()
if(forloop!=null) return forloop if(forloop!=null) return forloop
val repeatloop = repeatloop()?.toAst() val untilloop = untilloop()?.toAst()
if(repeatloop!=null) return repeatloop if(untilloop!=null) return untilloop
val whileloop = whileloop()?.toAst() val whileloop = whileloop()?.toAst()
if(whileloop!=null) return whileloop if(whileloop!=null) return whileloop
val foreverloop = foreverloop()?.toAst() val repeatloop = repeatloop()?.toAst()
if(foreverloop!=null) return foreverloop if(repeatloop!=null) return repeatloop
val breakstmt = breakstmt()?.toAst() val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt if(breakstmt!=null) return breakstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
val whenstmt = whenstmt()?.toAst() val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt if(whenstmt!=null) return whenstmt
@ -247,7 +245,7 @@ private class AsmsubDecl(val name: String,
val returntypes: List<DataType>, val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>, val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>, val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<Register>) val asmClobbers: Set<CpuRegister>)
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl { private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
val name = identifier().text val name = identifier().text
@ -256,7 +254,7 @@ private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet() val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) } val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
val normalReturntypes = returns.map { it.type } val normalReturntypes = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) } val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, false) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) } val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers) return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
} }
@ -265,7 +263,7 @@ private class AsmSubroutineParameter(name: String,
type: DataType, type: DataType,
val registerOrPair: RegisterOrPair?, val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?, val statusflag: Statusflag?,
val stack: Boolean, // TODO implement: val stack: Boolean,
position: Position) : SubroutineParameter(name, type, position) position: Position) : SubroutineParameter(name, type, position)
private class AsmSubroutineReturn(val type: DataType, private class AsmSubroutineReturn(val type: DataType,
@ -274,24 +272,42 @@ private class AsmSubroutineReturn(val type: DataType,
val stack: Boolean, val stack: Boolean,
val position: Position) val position: Position)
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
= this.register().asSequence().map { it.toAst() }.toSet()
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn> private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) } = asmsub_return().map {
val register = it.identifier()?.toAst()
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (val name = register.nameInSource.single()) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
else -> throw FatalAstException("invalid register or status flag in $it")
}
}
AsmSubroutineReturn(
it.datatype().toAst(),
registerorpair,
statusregister,
!it.stack?.text.isNullOrEmpty(), toPosition())
}
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter> private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map { = asmsub_param().map {
val vardecl = it.vardecl() val vardecl = it.vardecl()
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
AsmSubroutineParameter(vardecl.varname.text, datatype, val register = it.identifier()?.toAst()
it.registerorpair()?.toAst(), var registerorpair: RegisterOrPair? = null
it.statusregister()?.toAst(), var statusregister: Statusflag? = null
!it.stack?.text.isNullOrEmpty(), toPosition()) if(register!=null) {
when (val name = register.nameInSource.single()) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
else -> throw FatalAstException("invalid register or status flag '$name'")
}
}
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
} }
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement { private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
val void = this.VOID() != null val void = this.VOID() != null
val location = scoped_identifier().toAst() val location = scoped_identifier().toAst()
@ -350,23 +366,22 @@ private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
} }
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget { private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
val register = register()?.toAst()
val identifier = scoped_identifier() val identifier = scoped_identifier()
return when { return when {
register!=null -> AssignTarget(register, null, null, null, toPosition()) identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition()) arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition()) directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition()) else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition())
} }
} }
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase()) private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
val names = this.identifier().map { it.toAst().nameInSource.single() }
return names.map { CpuRegister.valueOf(it) }.toSet()
}
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase()) private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex = private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition()) ArrayIndex(expression().toAst(), toPosition())
@ -382,7 +397,7 @@ private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
} }
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral { 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 val integer: Int
var datatype = DataType.UBYTE var datatype = DataType.UBYTE
when (radix) { when (radix) {
@ -420,14 +435,14 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
} }
else -> throw FatalAstException("invalid radix") 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 terminal: TerminalNode = children[0] as TerminalNode
val integerPart = this.intpart.text val integerPart = this.intpart.text
return when (terminal.symbol.type) { return when (terminal.symbol.type) {
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null) prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10)
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null) prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null) prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
else -> throw FatalAstException(terminal.text) else -> throw FatalAstException(terminal.text)
} }
} }
@ -456,7 +471,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
litval.charliteral()!=null -> { litval.charliteral()!=null -> {
try { try {
val cc=litval.charliteral() val cc=litval.charliteral()
NumericLiteralValue(DataType.UBYTE, CompilationTarget.encodeString( NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()), unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition()) litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
} catch (ce: CharConversionException) { } catch (ce: CharConversionException) {
@ -469,18 +484,11 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
// the ConstantFold takes care of that and converts the type if needed. // the ConstantFold takes care of that and converts the type if needed.
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition()) ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
} }
litval.structliteral()!=null -> {
val values = litval.structliteral().expression().map { it.toAst() }
StructLiteralValue(values, litval.toPosition())
}
else -> throw FatalAstException("invalid parsed literal") else -> throw FatalAstException("invalid parsed literal")
} }
} }
} }
if(register()!=null)
return RegisterExpr(register().toAst(), register().toPosition())
if(scoped_identifier()!=null) if(scoped_identifier()!=null)
return scoped_identifier().toAst() return scoped_identifier().toAst()
@ -572,19 +580,16 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase()) private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
private fun prog8Parser.ForloopContext.toAst(): ForLoop { private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst() val loopvar = identifier().toAst()
val loopvar = identifier()?.toAst()
val iterable = expression()!!.toAst() val iterable = expression()!!.toAst()
val scope = val scope =
if(statement()!=null) if(statement()!=null)
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition()) AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
else else
AnonymousScope(statement_block().toAst(), statement_block().toPosition()) AnonymousScope(statement_block().toAst(), statement_block().toPosition())
return ForLoop(loopregister, loopvar, iterable, scope, toPosition()) return ForLoop(loopvar, iterable, scope, toPosition())
} }
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition()) private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop { private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
@ -595,19 +600,20 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
return WhileLoop(condition, scope, toPosition()) return WhileLoop(condition, scope, toPosition())
} }
private fun prog8Parser.ForeverloopContext.toAst(): ForeverLoop { private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
val iterations = expression()?.toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition() val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition()) ?: statement().toPosition())
return ForeverLoop(scope, toPosition()) return RepeatLoop(iterations, scope, toPosition())
} }
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop { private fun prog8Parser.UntilloopContext.toAst(): UntilLoop {
val untilCondition = expression().toAst() val untilCondition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition() val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition()) ?: statement().toPosition())
return RepeatLoop(scope, untilCondition, toPosition()) return UntilLoop(scope, untilCondition, toPosition())
} }
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement { private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {

View File

@ -21,10 +21,9 @@ enum class DataType {
STRUCT; // pass by reference STRUCT; // pass by reference
/** /**
* is the type assignable to the given other type? * is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
*/ */
infix fun isAssignableTo(targetType: DataType) = infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others, perhaps via a typecast, without loss of precision?
when(this) { when(this) {
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT) UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
BYTE -> targetType in setOf(BYTE, WORD, FLOAT) BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
@ -57,14 +56,14 @@ enum class DataType {
return when(this) { return when(this) {
in ByteDatatypes -> 1 in ByteDatatypes -> 1
in WordDatatypes -> 2 in WordDatatypes -> 2
FLOAT -> CompilationTarget.machine.FLOAT_MEM_SIZE FLOAT -> CompilationTarget.instance.machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> 2 in PassByReferenceDatatypes -> CompilationTarget.instance.machine.POINTER_MEM_SIZE
else -> -9999999 else -> -9999999
} }
} }
} }
enum class Register { enum class CpuRegister {
A, A,
X, X,
Y Y
@ -76,14 +75,23 @@ enum class RegisterOrPair {
Y, Y,
AX, AX,
AY, AY,
XY XY;
companion object {
val names by lazy { values().map { it.toString()} }
}
} // only used in parameter and return value specs in asm subroutines } // only used in parameter and return value specs in asm subroutines
enum class Statusflag { enum class Statusflag {
Pc, Pc,
Pz, Pz,
Pv, Pv,
Pn Pn;
companion object {
val names by lazy { values().map { it.toString()} }
}
} }
enum class BranchCondition { enum class BranchCondition {
@ -157,6 +165,7 @@ object ParentSentinel : Node {
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) { 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}]" override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
fun toClickableStr(): String = "$file:$line:$startCol:"
companion object { companion object {
val DUMMY = Position("<dummy>", 0, 0, 0) 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.ERROR -> System.err.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow 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) { if(msg !in alreadyReportedMessages) {
System.err.println(msg) System.err.println(msg)
alreadyReportedMessages.add(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 AstException (override var message: String) : Exception(message)
open class SyntaxError(override var message: String, val position: Position) : AstException(message) { open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Syntax error: $message" override fun toString() = "${position.toClickableStr()} Syntax error: $message"
} }
class ExpressionError(message: String, val position: Position) : AstException(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) class UndefinedSymbolError(symbol: IdentifierReference)

View File

@ -5,7 +5,6 @@ import prog8.ast.Program
import prog8.ast.processing.* import prog8.ast.processing.*
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.BeforeAsmGenerationAstChanger import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.optimizer.AssignmentTransformer
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) { internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
@ -25,12 +24,6 @@ internal fun Program.reorderStatements() {
reorder.applyModifications() reorder.applyModifications()
} }
internal fun Program.inlineSubroutines(): Int {
val reorder = SubroutineInliner(this)
reorder.visit(this)
return reorder.applyModifications()
}
internal fun Program.addTypecasts(errors: ErrorReporter) { internal fun Program.addTypecasts(errors: ErrorReporter) {
val caster = TypecastsAdder(this, errors) val caster = TypecastsAdder(this, errors)
caster.visit(this) caster.visit(this)
@ -42,17 +35,6 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this) fixer.visit(this)
} }
internal fun Program.transformAssignments(errors: ErrorReporter) {
val transform = AssignmentTransformer(this, errors)
transform.visit(this)
while(transform.optimizationsDone>0 && errors.isEmpty()) {
transform.applyModifications()
transform.optimizationsDone = 0
transform.visit(this)
}
transform.applyModifications()
}
internal fun Module.checkImportedValid() { internal fun Module.checkImportedValid() {
val imr = ImportedModuleDirectiveRemover() val imr = ImportedModuleDirectiveRemover()
imr.visit(this, this.parent) imr.visit(this, this.parent)

View File

@ -16,34 +16,52 @@ import kotlin.math.abs
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=") val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>")
sealed class Expression: Node { sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue? abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstVisitor) abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node) 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 abstract fun inferType(program: Program): InferredTypes.InferredType
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
infix fun isSameAs(other: Expression): Boolean { infix fun isSameAs(other: Expression): Boolean {
if(this===other) if(this===other)
return true return true
when(this) { return when(this) {
is RegisterExpr ->
return (other is RegisterExpr && other.register==register)
is IdentifierReference -> is IdentifierReference ->
return (other is IdentifierReference && other.nameInSource==nameInSource) (other is IdentifierReference && other.nameInSource==nameInSource)
is PrefixExpression -> is PrefixExpression ->
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression) (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
is BinaryExpression -> is BinaryExpression ->
return (other is BinaryExpression && other.operator==operator (other is BinaryExpression && other.operator==operator
&& other.left isSameAs left && other.left isSameAs left
&& other.right isSameAs right) && other.right isSameAs right)
is ArrayIndexedExpression -> { is ArrayIndexedExpression -> {
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index) && other.arrayspec.index isSameAs arrayspec.index)
} }
else -> return other==this is DirectMemoryRead -> {
(other is DirectMemoryRead && other.addressExpression isSameAs addressExpression)
}
is TypecastExpression -> {
(other is TypecastExpression && other.implicit==implicit && other.type==type && other.expression isSameAs expression)
}
is AddressOf -> {
(other is AddressOf && other.identifier.nameInSource == identifier.nameInSource)
}
is RangeExpr -> {
(other is RangeExpr && other.from==from && other.to==to && other.step==step)
}
is FunctionCall -> {
(other is FunctionCall && other.target.nameInSource == target.nameInSource
&& other.args.size == args.size
&& other.args.zip(args).all { it.first isSameAs it.second } )
}
else -> other==this
} }
} }
} }
@ -67,7 +85,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) 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 { override fun inferType(program: Program): InferredTypes.InferredType {
val inferred = expression.inferType(program) val inferred = expression.inferType(program)
return when(operator) { return when(operator) {
@ -124,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: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) 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 { override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program) val leftDt = left.inferType(program)
val rightDt = right.inferType(program) val rightDt = right.inferType(program)
@ -237,7 +255,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name) override fun referencesIdentifier(vararg scopedName: String) = identifier.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType { override fun inferType(program: Program): InferredTypes.InferredType {
val target = identifier.targetStatement(program.namespace) val target = identifier.targetStatement(program.namespace)
@ -273,13 +291,15 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) 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 inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? { override fun constValue(program: Program): NumericLiteralValue? {
val cv = expression.constValue(program) ?: return null val cv = expression.constValue(program) ?: return null
return cv.cast(type) val cast = cv.cast(type)
// val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type) return if(cast.isValid)
// return LiteralValue.fromNumber(value.numericValue(), value.type, position).cast(type) cast.valueOrZero()
else
null
} }
override fun toString(): String { override fun toString(): String {
@ -302,7 +322,7 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
} }
override fun constValue(program: Program): NumericLiteralValue? = null 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 inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -325,7 +345,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) 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 inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
@ -378,7 +398,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
throw FatalAstException("can't replace here") 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 constValue(program: Program) = this
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
@ -398,87 +418,66 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble()) operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
fun cast(targettype: DataType): NumericLiteralValue { class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) {
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY)
}
fun cast(targettype: DataType): CastValue {
if(type==targettype) if(type==targettype)
return this return CastValue(true, this)
val numval = number.toDouble() val numval = number.toDouble()
when(type) { when(type) {
DataType.UBYTE -> { DataType.UBYTE -> {
if(targettype== DataType.BYTE && numval <= 127) if(targettype== DataType.BYTE && numval <= 127)
return NumericLiteralValue(targettype, number.toShort(), position) return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.WORD || targettype== DataType.UWORD) if(targettype== DataType.WORD || targettype== DataType.UWORD)
return NumericLiteralValue(targettype, number.toInt(), position) return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.FLOAT) if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position) return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
} }
DataType.BYTE -> { DataType.BYTE -> {
if(targettype== DataType.UBYTE && numval >= 0) if(targettype== DataType.UBYTE && numval >= 0)
return NumericLiteralValue(targettype, number.toShort(), position) return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UWORD && numval >= 0) if(targettype== DataType.UWORD && numval >= 0)
return NumericLiteralValue(targettype, number.toInt(), position) return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.WORD) if(targettype== DataType.WORD)
return NumericLiteralValue(targettype, number.toInt(), position) return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.FLOAT) if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position) return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
} }
DataType.UWORD -> { DataType.UWORD -> {
if(targettype== DataType.BYTE && numval <= 127) if(targettype== DataType.BYTE && numval <= 127)
return NumericLiteralValue(targettype, number.toShort(), position) return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UBYTE && numval <= 255) if(targettype== DataType.UBYTE && numval <= 255)
return NumericLiteralValue(targettype, number.toShort(), position) return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.WORD && numval <= 32767) if(targettype== DataType.WORD && numval <= 32767)
return NumericLiteralValue(targettype, number.toInt(), position) return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.FLOAT) if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position) return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
} }
DataType.WORD -> { DataType.WORD -> {
if(targettype== DataType.BYTE && numval >= -128 && numval <=127) if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
return NumericLiteralValue(targettype, number.toShort(), position) return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255) if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
return NumericLiteralValue(targettype, number.toShort(), position) return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UWORD && numval >=0) if(targettype== DataType.UWORD && numval >=0)
return NumericLiteralValue(targettype, number.toInt(), position) return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.FLOAT) if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position) return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
} }
DataType.FLOAT -> { DataType.FLOAT -> {
if (targettype == DataType.BYTE && numval >= -128 && numval <=127) if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
return NumericLiteralValue(targettype, number.toShort(), position) return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255) if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
return NumericLiteralValue(targettype, number.toShort(), position) return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767) if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
return NumericLiteralValue(targettype, number.toInt(), position) return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535) if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
return NumericLiteralValue(targettype, number.toInt(), position) return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
} }
else -> {} else -> {}
} }
throw ExpressionError("can't cast $type into $targettype", position) return CastValue(false, null)
}
}
class StructLiteralValue(var values: List<Expression>,
override val position: Position): Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent=parent
values.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
override fun toString(): String {
return "struct{ ${values.joinToString(", ")} }"
} }
} }
@ -499,7 +498,7 @@ class StringLiteralValue(val value: String,
throw FatalAstException("can't replace here") 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 constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -529,12 +528,12 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression) require(replacement is Expression)
val idx = value.withIndex().find { it.value===node }!!.index val idx = value.indexOfFirst { it===node }
value[idx] = replacement value[idx] = replacement
replacement.parent = this 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 constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
@ -570,10 +569,17 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) } val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when { return when {
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F) 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.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW) DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B) DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB) 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() else -> InferredTypes.InferredType.unknown()
} }
} }
@ -588,14 +594,14 @@ class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred be
if(num==null) { if(num==null) {
// an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted // an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted
if (elementType != DataType.UWORD || it !is AddressOf) if (elementType != DataType.UWORD || it !is AddressOf)
return null return null // can't cast a value of the array, abort
it it
} else { } else {
try { val cast = num.cast(elementType)
num.cast(elementType) if(cast.isValid)
} catch(x: ExpressionError) { cast.valueOrZero()
return null else
} return null // can't cast a value of the array, abort
} }
}.toTypedArray() }.toTypedArray()
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position) return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
@ -632,7 +638,7 @@ class RangeExpr(var from: Expression,
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) 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 { override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program) val fromDt=from.inferType(program)
val toDt=to.inferType(program) val toDt=to.inferType(program)
@ -643,7 +649,14 @@ class RangeExpr(var from: Expression,
fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR) fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W) fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W)
fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B) fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B)
else -> InferredTypes.knownFor(DataType.ARRAY_UB) else -> {
val fdt = fromDt.typeOrElse(DataType.STRUCT)
val tdt = toDt.typeOrElse(DataType.STRUCT)
if(fdt largerThan tdt)
InferredTypes.knownFor(ElementArrayTypes.getValue(fdt))
else
InferredTypes.knownFor(ElementArrayTypes.getValue(tdt))
}
} }
} }
override fun toString(): String { override fun toString(): String {
@ -665,8 +678,8 @@ class RangeExpr(var from: Expression,
val toString = to as? StringLiteralValue val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) { if(fromString!=null && toString!=null ) {
// string range -> int range over character values // string range -> int range over character values
fromVal = CompilationTarget.encodeString(fromString.value, fromString.altEncoding)[0].toInt() fromVal = CompilationTarget.instance.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = CompilationTarget.encodeString(toString.value, fromString.altEncoding)[0].toInt() toVal = CompilationTarget.instance.encodeString(toString.value, fromString.altEncoding)[0].toInt()
} else { } else {
val fromLv = from as? NumericLiteralValue val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue val toLv = to as? NumericLiteralValue
@ -696,29 +709,6 @@ internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
} }
} }
class RegisterExpr(val register: Register, override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
override fun toString(): String {
return "RegisterExpr(register=$register, pos=$position)"
}
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
}
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable { data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node override lateinit var parent: Node
@ -732,6 +722,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
override fun hashCode() = nameInSource.hashCode()
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -760,14 +751,14 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) 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 { override fun inferType(program: Program): InferredTypes.InferredType {
val targetStmt = targetStatement(program.namespace) return when (val targetStmt = targetStatement(program.namespace)) {
return if(targetStmt is VarDecl) { is VarDecl -> InferredTypes.knownFor(targetStmt.datatype)
InferredTypes.knownFor(targetStmt.datatype) is StructDecl -> InferredTypes.knownFor(DataType.STRUCT)
} else { else -> InferredTypes.InferredType.unknown()
InferredTypes.InferredType.unknown()
} }
} }
@ -783,6 +774,18 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
else -> throw FatalAstException("requires a reference value") 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, class FunctionCall(override var target: IdentifierReference,
@ -800,7 +803,7 @@ class FunctionCall(override var target: IdentifierReference,
if(node===target) if(node===target)
target=replacement as IdentifierReference target=replacement as IdentifierReference
else { else {
val idx = args.withIndex().find { it.value===node }!!.index val idx = args.indexOfFirst { it===node }
args[idx] = replacement as Expression args[idx] = replacement as Expression
} }
replacement.parent = this replacement.parent = this
@ -849,7 +852,7 @@ class FunctionCall(override var target: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent) 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 { override fun inferType(program: Program): InferredTypes.InferredType {
val constVal = constValue(program ,false) val constVal = constValue(program ,false)

View File

@ -7,7 +7,9 @@ import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import java.io.File import java.io.File
@ -110,48 +112,57 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(forLoop: ForLoop) { override fun visit(forLoop: ForLoop) {
if(forLoop.body.containsNoCodeNorVars())
errors.warn("for loop body is empty", forLoop.position)
val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE) val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE)
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) { if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
errors.err("can only loop over an iterable type", forLoop.position) errors.err("can only loop over an iterable type", forLoop.position)
} else { } else {
if (forLoop.loopRegister != null) { val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)
// loop register if(loopvar==null || loopvar.type== VarDeclType.CONST) {
if (iterableDt != DataType.ARRAY_UB && iterableDt != DataType.ARRAY_B && iterableDt != DataType.STR) errors.err("for loop requires a variable to loop with", forLoop.position)
errors.err("register can only loop over bytes", forLoop.position)
if(forLoop.loopRegister!=Register.A)
errors.err("it's only possible to use A as a loop register", forLoop.position)
} else { } else {
// loop variable
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace) fun checkLoopRangeValues() {
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
errors.err("for loop requires a variable to loop with", forLoop.position) }
} else {
when (loopvar.datatype) { when (loopvar.datatype) {
DataType.UBYTE -> { DataType.UBYTE -> {
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR) if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position) errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
} }
DataType.UWORD -> { DataType.UWORD -> {
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR && if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW) iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position) errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
} }
DataType.BYTE -> { DataType.BYTE -> {
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B) if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
errors.err("byte loop variable can only loop over bytes", forLoop.position) errors.err("byte loop variable can only loop over bytes", forLoop.position)
} }
DataType.WORD -> { DataType.WORD -> {
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.WORD && if(iterableDt!= DataType.BYTE && iterableDt!= DataType.WORD &&
iterableDt != DataType.ARRAY_B && iterableDt!= DataType.ARRAY_W) iterableDt != DataType.ARRAY_B && iterableDt!= DataType.ARRAY_W)
errors.err("word loop variable can only loop over bytes or words", forLoop.position) errors.err("word loop variable can only loop over bytes or words", forLoop.position)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
errors.err("for loop only supports integers", forLoop.position) errors.err("for loop only supports integers", forLoop.position)
} }
else -> errors.err("loop variable must be numeric type", forLoop.position) else -> errors.err("loop variable must be numeric type", forLoop.position)
}
if(errors.isEmpty()) {
// check loop range values
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
val from = range.from as? NumericLiteralValue
val to = range.to as? NumericLiteralValue
if(from != null)
checkValueTypeAndRange(loopvar.datatype, from)
else if(!range.from.inferType(program).istype(loopvar.datatype))
errors.err("range start value is incompatible with loop variable type", range.position)
if(to != null)
checkValueTypeAndRange(loopvar.datatype, to)
else if(!range.to.inferType(program).istype(loopvar.datatype))
errors.err("range end value is incompatible with loop variable type", range.position)
} }
} }
} }
@ -260,27 +271,27 @@ internal class AstChecker(private val program: Program,
} }
} }
val regCounts = mutableMapOf<Register, Int>().withDefault { 0 } val regCounts = mutableMapOf<CpuRegister, Int>().withDefault { 0 }
val statusflagCounts = mutableMapOf<Statusflag, Int>().withDefault { 0 } val statusflagCounts = mutableMapOf<Statusflag, Int>().withDefault { 0 }
fun countRegisters(from: Iterable<RegisterOrStatusflag>) { fun countRegisters(from: Iterable<RegisterOrStatusflag>) {
regCounts.clear() regCounts.clear()
statusflagCounts.clear() statusflagCounts.clear()
for(p in from) { for(p in from) {
when(p.registerOrPair) { when(p.registerOrPair) {
RegisterOrPair.A -> regCounts[Register.A]=regCounts.getValue(Register.A)+1 RegisterOrPair.A -> regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
RegisterOrPair.X -> regCounts[Register.X]=regCounts.getValue(Register.X)+1 RegisterOrPair.X -> regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
RegisterOrPair.Y -> regCounts[Register.Y]=regCounts.getValue(Register.Y)+1 RegisterOrPair.Y -> regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
RegisterOrPair.AX -> { RegisterOrPair.AX -> {
regCounts[Register.A]=regCounts.getValue(Register.A)+1 regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
regCounts[Register.X]=regCounts.getValue(Register.X)+1 regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
} }
RegisterOrPair.AY -> { RegisterOrPair.AY -> {
regCounts[Register.A]=regCounts.getValue(Register.A)+1 regCounts[CpuRegister.A]=regCounts.getValue(CpuRegister.A)+1
regCounts[Register.Y]=regCounts.getValue(Register.Y)+1 regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
} }
RegisterOrPair.XY -> { RegisterOrPair.XY -> {
regCounts[Register.X]=regCounts.getValue(Register.X)+1 regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
regCounts[Register.Y]=regCounts.getValue(Register.Y)+1 regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
} }
null -> null ->
if(p.statusflag!=null) if(p.statusflag!=null)
@ -316,26 +327,19 @@ internal class AstChecker(private val program: Program,
} else { } else {
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly // Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD). // Instead, their reference (address) should be passed (as an UWORD).
// The language has no typed pointers at this time.
if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) { if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword for their address, or access the variable from the outer scope directly.") err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword to receive their address, or access the variable from the outer scope directly.")
} }
} }
visitStatements(subroutine.statements)
} }
override fun visit(repeatLoop: RepeatLoop) { override fun visit(untilLoop: UntilLoop) {
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) if(untilLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
errors.warn("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position) errors.err("condition value should be an integer type", untilLoop.condition.position)
if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes) super.visit(untilLoop)
errors.err("condition value should be an integer type", repeatLoop.untilCondition.position)
super.visit(repeatLoop)
} }
override fun visit(whileLoop: WhileLoop) { override fun visit(whileLoop: WhileLoop) {
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
errors.warn("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes) if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
errors.err("condition value should be an integer type", whileLoop.condition.position) errors.err("condition value should be an integer type", whileLoop.condition.position)
super.visit(whileLoop) super.visit(whileLoop)
@ -361,9 +365,9 @@ internal class AstChecker(private val program: Program,
if(targetIdent!=null) { if(targetIdent!=null) {
val targetVar = targetIdent.targetVarDecl(program.namespace) val targetVar = targetIdent.targetVarDecl(program.namespace)
if(targetVar?.struct != null) { if(targetVar?.struct != null) {
val sourceStructLv = assignment.value as? StructLiteralValue val sourceStructLv = assignment.value as? ArrayLiteralValue
if (sourceStructLv != null) { if (sourceStructLv != null) {
if (sourceStructLv.values.size != targetVar.struct?.numberOfElements) if (sourceStructLv.value.size != targetVar.struct?.numberOfElements)
errors.err("number of elements doesn't match struct definition", sourceStructLv.position) errors.err("number of elements doesn't match struct definition", sourceStructLv.position)
} else { } else {
val sourceIdent = assignment.value as? IdentifierReference val sourceIdent = assignment.value as? IdentifierReference
@ -372,14 +376,27 @@ internal class AstChecker(private val program: Program,
if (sourceVar?.struct != null) { if (sourceVar?.struct != null) {
if (sourceVar.struct !== targetVar.struct) if (sourceVar.struct !== targetVar.struct)
errors.err("assignment of different struct types", assignment.position) 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)
} }
} }
} }
} }
} }
if(assignment.value.inferType(program) != assignment.target.inferType(program, assignment)) val targetDt = assignment.target.inferType(program, assignment)
errors.err("assignment value is of different type as the target", assignment.value.position) if(assignment.value.inferType(program) != targetDt) {
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
errors.err("cannot assign value to string or array", assignment.value.position)
else
errors.err("value's type doesn't match target", assignment.value.position)
}
if(assignment.value is TypecastExpression) {
if(assignment.isAugmentable && targetDt.istype(DataType.FLOAT))
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
}
super.visit(assignment) super.visit(assignment)
} }
@ -397,8 +414,7 @@ internal class AstChecker(private val program: Program,
val targetIdentifier = assignTarget.identifier val targetIdentifier = assignTarget.identifier
if (targetIdentifier != null) { if (targetIdentifier != null) {
val targetName = targetIdentifier.nameInSource val targetName = targetIdentifier.nameInSource
val targetSymbol = program.namespace.lookup(targetName, assignment) when (val targetSymbol = program.namespace.lookup(targetName, assignment)) {
when (targetSymbol) {
null -> { null -> {
errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position) errors.err("undefined symbol: ${targetIdentifier.nameInSource.joinToString(".")}", targetIdentifier.position)
return return
@ -451,21 +467,20 @@ internal class AstChecker(private val program: Program,
if(variable==null) if(variable==null)
errors.err("pointer-of operand must be the name of a heap variable", addressOf.position) errors.err("pointer-of operand must be the name of a heap variable", addressOf.position)
else { else {
if(variable.datatype !in ArrayDatatypes && variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT) if(variable.datatype !in ArrayDatatypes
&& variable.type!=VarDeclType.MEMORY
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
errors.err("invalid pointer-of operand type", addressOf.position) errors.err("invalid pointer-of operand type", addressOf.position)
} }
super.visit(addressOf) super.visit(addressOf)
} }
override fun visit(decl: VarDecl) { override fun visit(decl: VarDecl) {
fun err(msg: String, position: Position?=null) { fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
errors.err(msg, position ?: decl.position)
}
// the initializer value can't refer to the variable itself (recursive definition) // the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) { if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true)
err("recursive var declaration") err("recursive var declaration")
}
// CONST can only occur on simple types (byte, word, float) // CONST can only occur on simple types (byte, word, float)
if(decl.type== VarDeclType.CONST) { if(decl.type== VarDeclType.CONST) {
@ -473,10 +488,12 @@ internal class AstChecker(private val program: Program,
err("const modifier can only be used on numeric types (byte, word, float)") err("const modifier can only be used on numeric types (byte, word, float)")
} }
// FLOATS // FLOATS enabled?
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY) { if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
err("floating point used, but that is not enabled via options") err("floating point used, but that is not enabled via options")
}
if(decl.datatype == DataType.FLOAT && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE))
errors.warn("floating point values won't be placed in Zeropage due to size constraints", decl.position)
// ARRAY without size specifier MUST have an iterable initializer value // ARRAY without size specifier MUST have an iterable initializer value
if(decl.isArray && decl.arraysize==null) { if(decl.isArray && decl.arraysize==null) {
@ -509,29 +526,21 @@ internal class AstChecker(private val program: Program,
when(decl.value) { when(decl.value) {
null -> { null -> {
// a vardecl without an initial value, don't bother with the rest // a vardecl without an initial value, don't bother with it
return super.visit(decl)
} }
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value") is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value")
is StringLiteralValue -> { is StringLiteralValue -> {
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue) checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
} }
is ArrayLiteralValue -> { is ArrayLiteralValue -> {
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
}
is NumericLiteralValue -> {
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
}
is StructLiteralValue -> {
if(decl.datatype==DataType.STRUCT) { if(decl.datatype==DataType.STRUCT) {
val struct = decl.struct!! val struct = decl.struct!!
val structLv = decl.value as StructLiteralValue val structLv = decl.value as ArrayLiteralValue
if(struct.numberOfElements != structLv.values.size) { if(struct.numberOfElements != structLv.value.size) {
errors.err("struct value has incorrect number of elements", structLv.position) errors.err("struct value has incorrect number of elements", structLv.position)
return return
} }
for(value in structLv.values.zip(struct.statements)) { for(value in structLv.value.zip(struct.statements)) {
val memberdecl = value.second as VarDecl val memberdecl = value.second as VarDecl
val constValue = value.first.constValue(program) val constValue = value.first.constValue(program)
if(constValue==null) { if(constValue==null) {
@ -545,19 +554,25 @@ internal class AstChecker(private val program: Program,
} }
} }
} else { } else {
errors.err("struct literal is wrong type to initialize this variable", decl.value!!.position) val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
} }
} }
is NumericLiteralValue -> {
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
}
else -> { else -> {
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!.javaClass.simpleName}") if(decl.type==VarDeclType.CONST) {
super.visit(decl) err("const declaration needs a compile-time constant initializer value, or range")
return super.visit(decl)
return
}
} }
} }
} }
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
if(decl.arraysize!=null) { if(decl.arraysize!=null) {
val arraySize = decl.arraysize!!.size() ?: 1 val arraySize = decl.arraysize!!.constIndex() ?: 1
when(decl.datatype) { when(decl.datatype) {
DataType.ARRAY_B, DataType.ARRAY_UB -> DataType.ARRAY_B, DataType.ARRAY_UB ->
if(arraySize > 256) if(arraySize > 256)
@ -572,20 +587,50 @@ internal class AstChecker(private val program: Program,
} }
} }
if(decl.value !is NumericLiteralValue) { 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 {
val value = decl.value as NumericLiteralValue val value = decl.value as NumericLiteralValue
if (value.type !in IntegerDatatypes || value.number.toInt() < 0 || value.number.toInt() > 65535) { 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) 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)
} }
} }
} }
val declValue = decl.value val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR && !declValue.inferType(program).istype(decl.datatype)) if(declValue!=null && decl.type==VarDeclType.VAR) {
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position) if(decl.datatype==DataType.STRUCT) {
val valueIdt = declValue.inferType(program)
if(valueIdt.isUnknown)
throw AstException("invalid value type")
val valueDt = valueIdt.typeOrElse(DataType.STRUCT)
if(valueDt !in ArrayDatatypes)
err("initialisation of struct should be with array value", declValue.position)
} else if (!declValue.inferType(program).istype(decl.datatype)) {
err("initialisation value has incompatible type (${declValue.inferType(program)}) for the variable (${decl.datatype})", declValue.position)
}
}
// array length limits
if(decl.isArray) {
val length = decl.arraysize!!.constIndex() ?: 1
when (decl.datatype) {
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
if(length==0 || length>256)
err("string and byte array length must be 1-256")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
if(length==0 || length>128)
err("word array length must be 1-128")
}
DataType.ARRAY_F -> {
if(length==0 || length>51)
err("float array length must be 1-51")
}
else -> {}
}
}
super.visit(decl) super.visit(decl)
} }
@ -667,9 +712,17 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level") err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty()) if(directive.args.isEmpty())
err("missing option directive argument(s)") 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)") err("invalid option directive argument(s)")
} }
"%target" -> {
if(directive.parent !is Block && directive.parent !is Module)
err("this directive may only occur in a block or at module level")
if(directive.args.size != 1)
err("directive requires one argument")
if(directive.args.single().name !in setOf(C64Target.name, Cx16Target.name))
err("invalid compilation target")
}
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position) else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
} }
super.visit(directive) super.visit(directive)
@ -692,6 +745,17 @@ internal class AstChecker(private val program: Program,
checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array) checkValueTypeAndRangeArray(array.type.typeOrElse(DataType.STRUCT), null, arrayspec, array)
} }
fun isPassByReferenceElement(e: Expression): Boolean {
if(e is IdentifierReference) {
val decl = e.targetVarDecl(program.namespace)!!
return decl.datatype in PassByReferenceDatatypes
}
return e is StringLiteralValue
}
if(!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
errors.err("array literal contains invalid types", array.position)
super.visit(array) super.visit(array)
} }
@ -701,12 +765,20 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(expr: PrefixExpression) { override fun visit(expr: PrefixExpression) {
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
if(expr.operator=="-") { if(expr.operator=="-") {
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) { if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
errors.err("can only take negative of a signed number type", expr.position) errors.err("can only take negative of a signed number type", expr.position)
} }
} }
else if(expr.operator == "not") {
if(dt !in IntegerDatatypes)
errors.err("can only use boolean not on integer types", expr.position)
}
else if(expr.operator == "~") {
if(dt !in IntegerDatatypes)
errors.err("can only use bitwise invert on integer types", expr.position)
}
super.visit(expr) super.visit(expr)
} }
@ -748,12 +820,6 @@ internal class AstChecker(private val program: Program,
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes) if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
errors.err("bitwise operator can only be used on integer operands", expr.right.position) errors.err("bitwise operator can only be used on integer operands", expr.right.position)
} }
"<<", ">>" -> {
// for now, bit-shifts can only shift by a constant number
val constRight = expr.right.constValue(program)
if(constRight==null)
errors.err("bit-shift can only be done by a constant number (for now)", expr.right.position)
}
} }
if(leftDt !in NumericDatatypes) if(leftDt !in NumericDatatypes)
@ -820,6 +886,10 @@ internal class AstChecker(private val program: Program,
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position) errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
} }
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program)
if(error!=null)
errors.err(error, functionCall.args.first().position)
super.visit(functionCall) super.visit(functionCall)
} }
@ -842,12 +912,18 @@ internal class AstChecker(private val program: Program,
} }
} }
if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) { if(functionCallStatement.target.nameInSource.last() in setOf("rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
// in-place modification, can't be done on literals // in-place modification, can't be done on literals
if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) { if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position) errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
} }
} }
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, functionCallStatement.definingScope(), program)
if(error!=null) {
errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position)
}
super.visit(functionCallStatement) super.visit(functionCallStatement)
} }
@ -856,79 +932,35 @@ internal class AstChecker(private val program: Program,
errors.err("cannot use arguments when calling a label", position) errors.err("cannot use arguments when calling a label", position)
if(target is BuiltinFunctionStatementPlaceholder) { if(target is BuiltinFunctionStatementPlaceholder) {
// it's a call to a builtin function. if(target.name=="swap") {
val func = BuiltinFunctions.getValue(target.name) // swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
if(args.size!=func.parameters.size) val dt1 = args[0].inferType(program)
errors.err("invalid number of arguments", position) val dt2 = args[1].inferType(program)
else { if (dt1 != dt2)
val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD errors.err("swap requires 2 args of identical type", position)
for (arg in args.withIndex().zip(func.parameters)) { else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
val argDt=arg.first.value.inferType(program) errors.err("swap requires 2 variables, not constant value(s)", position)
if (argDt.isKnown else if(args[0] isSameAs args[1])
&& !(argDt.typeOrElse(DataType.STRUCT) isAssignableTo arg.second.possibleDatatypes) errors.err("swap should have 2 different args", position)
&& (argDt.typeOrElse(DataType.STRUCT) != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) { else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
errors.err("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position) errors.err("swap requires args of numerical type", position)
} }
else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
} }
if(target.name=="swap") { if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
val dt1 = args[0].inferType(program)
val dt2 = args[1].inferType(program)
if (dt1 != dt2)
errors.err("swap requires 2 args of identical type", position)
else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
errors.err("swap requires 2 variables, not constant value(s)", position)
else if(args[0] isSameAs args[1])
errors.err("swap should have 2 different args", position)
else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
errors.err("swap requires args of numerical type", position)
}
else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
}
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
}
} }
} }
} else if(target is Subroutine) { } else if(target is Subroutine) {
if(target.regXasResult()) if(target.regXasResult())
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position) errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
if(args.size!=target.parameters.size) if(target.isAsmSubroutine) {
errors.err("invalid number of arguments", position)
else {
for (arg in args.withIndex().zip(target.parameters)) { for (arg in args.withIndex().zip(target.parameters)) {
val argIDt = arg.first.value.inferType(program) val argIDt = arg.first.value.inferType(program)
if(!argIDt.isKnown) { if (!argIDt.isKnown)
return return
}
val argDt=argIDt.typeOrElse(DataType.STRUCT)
if(!(argDt isAssignableTo arg.second.type)) {
// for asm subroutines having STR param it's okay to provide a UWORD (address value)
if(!(target.isAsmSubroutine && arg.second.type == DataType.STR && argDt == DataType.UWORD))
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position)
}
if(target.isAsmSubroutine) {
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
if (arg.first.value !is NumericLiteralValue && arg.first.value !is IdentifierReference)
errors.warn("calling a subroutine that expects X as a parameter is problematic. If you see a compiler error/crash about this later, try to change this call", position)
}
// check if the argument types match the register(pairs)
val asmParamReg = target.asmParameterRegisters[arg.first.index]
if(asmParamReg.statusflag!=null) {
if(argDt !in ByteDatatypes)
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for statusflag", position)
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if(argDt !in ByteDatatypes)
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be byte type for single register", position)
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if(argDt !in WordDatatypes + IterableDatatypes)
errors.err("subroutine '${target.name}' argument ${arg.first.index + 1} must be word type for register pair", position)
}
}
} }
} }
} }
@ -968,7 +1000,7 @@ internal class AstChecker(private val program: Program,
if(target is VarDecl) { if(target is VarDecl) {
if(target.datatype !in IterableDatatypes) if(target.datatype !in IterableDatatypes)
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position) errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
val arraysize = target.arraysize?.size() val arraysize = target.arraysize?.constIndex()
if(arraysize!=null) { if(arraysize!=null) {
// check out of bounds // check out of bounds
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt() val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
@ -1049,35 +1081,14 @@ internal class AstChecker(private val program: Program,
} }
} }
override fun visit(scope: AnonymousScope) {
visitStatements(scope.statements)
}
private fun visitStatements(statements: List<Statement>) {
for((index, stmt) in statements.withIndex()) {
if(index < statements.lastIndex && statements[index+1] !is Subroutine) {
when {
stmt is FunctionCallStatement && stmt.target.nameInSource.last() == "exit" -> {
errors.warn("unreachable code, preceding exit call will never return", statements[index + 1].position)
}
stmt is Return && statements[index + 1] !is Subroutine -> {
errors.warn("unreachable code, preceding return statement", statements[index + 1].position)
}
stmt is Jump && statements[index + 1] !is Subroutine -> {
errors.warn("unreachable code, preceding jump statement", statements[index + 1].position)
}
}
}
stmt.accept(this)
}
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? { private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
val targetStatement = target.targetStatement(program.namespace) val targetStatement = target.targetStatement(program.namespace)
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder) if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
return targetStatement return targetStatement
errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position) else if(targetStatement==null)
errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)
else
errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position)
return null return null
} }
@ -1101,7 +1112,7 @@ internal class AstChecker(private val program: Program,
} }
if(value.type.isUnknown) if(value.type.isUnknown)
return err("attempt to check values of array with as yet unknown datatype") return false
when (targetDt) { when (targetDt) {
DataType.STR -> return err("string value expected") DataType.STR -> return err("string value expected")
@ -1110,7 +1121,7 @@ internal class AstChecker(private val program: Program,
if(value.type.istype(targetDt)) { if(value.type.istype(targetDt)) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySpecSize = arrayspec.size() val arraySpecSize = arrayspec.constIndex()
val arraySize = value.value.size val arraySize = value.value.size
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>256) if(arraySpecSize<1 || arraySpecSize>256)
@ -1132,7 +1143,7 @@ internal class AstChecker(private val program: Program,
if(value.type.istype(targetDt)) { if(value.type.istype(targetDt)) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySpecSize = arrayspec.size() val arraySpecSize = arrayspec.constIndex()
val arraySize = value.value.size val arraySize = value.value.size
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>128) if(arraySpecSize<1 || arraySpecSize>128)
@ -1155,7 +1166,7 @@ internal class AstChecker(private val program: Program,
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySize = value.value.size val arraySize = value.value.size
val arraySpecSize = arrayspec.size() val arraySpecSize = arrayspec.constIndex()
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize < 1 || arraySpecSize>51) if(arraySpecSize < 1 || arraySpecSize>51)
return err("float array length must be 1-51") return err("float array length must be 1-51")
@ -1170,7 +1181,7 @@ internal class AstChecker(private val program: Program,
// check if the floating point values are all within range // check if the floating point values are all within range
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray() val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
if(doubles.any { it < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || it > CompilationTarget.machine.FLOAT_MAX_POSITIVE }) if(doubles.any { it < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || it > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE })
return err("floating point value overflow") return err("floating point value overflow")
return true return true
} }
@ -1247,7 +1258,11 @@ internal class AstChecker(private val program: Program,
is AddressOf -> it.identifier.heapId(program.namespace) is AddressOf -> it.identifier.heapId(program.namespace)
is TypecastExpression -> { is TypecastExpression -> {
val constVal = it.expression.constValue(program) val constVal = it.expression.constValue(program)
constVal?.cast(it.type)?.number?.toInt() ?: -9999999 val cast = constVal?.cast(it.type)
if(cast==null || !cast.isValid)
-9999999
else
cast.valueOrZero().number.toInt()
} }
else -> -9999999 else -> -9999999
} }
@ -1292,8 +1307,8 @@ internal class AstChecker(private val program: Program,
DataType.STR -> sourceDatatype== DataType.STR DataType.STR -> sourceDatatype== DataType.STR
DataType.STRUCT -> { DataType.STRUCT -> {
if(sourceDatatype==DataType.STRUCT) { if(sourceDatatype==DataType.STRUCT) {
val structLv = sourceValue as StructLiteralValue val structLv = sourceValue as ArrayLiteralValue
val numValues = structLv.values.size val numValues = structLv.value.size
val targetstruct = target.identifier!!.targetVarDecl(program.namespace)!!.struct!! val targetstruct = target.identifier!!.targetVarDecl(program.namespace)!!.struct!!
return targetstruct.numberOfElements == numValues return targetstruct.numberOfElements == numValues
} }

View File

@ -22,6 +22,9 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
} }
override fun visit(block: Block) { override fun visit(block: Block) {
if(block.name in CompilationTarget.instance.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
val existing = blocks[block.name] val existing = blocks[block.name]
if(existing!=null) if(existing!=null)
nameError(block.name, block.position, existing) nameError(block.name, block.position, existing)
@ -31,13 +34,23 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
super.visit(block) 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) { override fun visit(decl: VarDecl) {
decl.datatypeErrors.forEach { errors.err(it.message, it.position) } decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
if(decl.name in BuiltinFunctions) if(decl.name in BuiltinFunctions)
errors.err("builtin function cannot be redefined", decl.position) errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in CompilationTarget.machine.opcodeNames) if(decl.name in CompilationTarget.instance.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
if(decl.datatype==DataType.STRUCT) { if(decl.datatype==DataType.STRUCT) {
@ -57,8 +70,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
return super.visit(decl) return super.visit(decl)
} }
if (decl.value != null && decl.value !is StructLiteralValue) { if (decl.value != null && decl.value !is ArrayLiteralValue) {
errors.err("initializing requires struct literal value", decl.value?.position ?: decl.position) errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
return super.visit(decl) return super.visit(decl)
} }
} }
@ -71,7 +84,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
} }
override fun visit(subroutine: Subroutine) { override fun visit(subroutine: Subroutine) {
if(subroutine.name in CompilationTarget.machine.opcodeNames) { if(subroutine.name in CompilationTarget.instance.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position) errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) { } else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined // the builtin functions can't be redefined
@ -85,14 +98,6 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if (existing != null && existing !== subroutine) if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing) 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 // check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
val symbolsInSub = subroutine.allDefinedSymbols() val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet() val namesInSub = symbolsInSub.map{ it.first }.toSet()
@ -116,7 +121,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
} }
override fun visit(label: Label) { override fun visit(label: Label) {
if(label.name in CompilationTarget.machine.opcodeNames) if(label.name in CompilationTarget.instance.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position) errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) { if(label.name in BuiltinFunctions) {
@ -137,21 +142,6 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
super.visit(label) super.visit(label)
} }
override fun visit(forLoop: ForLoop) {
if (forLoop.loopRegister != null) {
if (forLoop.loopRegister == Register.X)
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
}
super.visit(forLoop)
}
override fun visit(assignTarget: AssignTarget) {
if(assignTarget.register== Register.X)
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
super.visit(assignTarget)
}
override fun visit(string: StringLiteralValue) { override fun visit(string: StringLiteralValue) {
if (string.value.length !in 1..255) if (string.value.length !in 1..255)
errors.err("string literal length must be between 1 and 255", string.position) errors.err("string literal length must be between 1 and 255", string.position)

View File

@ -10,58 +10,6 @@ import prog8.ast.statements.*
internal class AstVariousTransforms(private val program: Program) : AstWalker() { internal class AstVariousTransforms(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource == listOf("swap")) {
// if x and y are both just identifiers, do not rewrite (there should be asm generation for that)
// otherwise:
// rewrite swap(x,y) as follows:
// - declare a temp variable of the same datatype
// - temp = x, x = y, y= temp
val first = functionCallStatement.args[0]
val second = functionCallStatement.args[1]
if(first !is IdentifierReference && second !is IdentifierReference) {
val dt = first.inferType(program).typeOrElse(DataType.STRUCT)
val tempname = "prog8_swaptmp_${functionCallStatement.hashCode()}"
val tempvardecl = VarDecl(VarDeclType.VAR, dt, ZeropageWish.DONTCARE, null, tempname, null, null, isArray = false, autogeneratedDontRemove = true, position = first.position)
val tempvar = IdentifierReference(listOf(tempname), first.position)
val assignTemp = Assignment(
AssignTarget(null, tempvar, null, null, first.position),
null,
first,
first.position
)
val assignFirst = Assignment(
AssignTarget.fromExpr(first),
null,
second,
first.position
)
val assignSecond = Assignment(
AssignTarget.fromExpr(second),
null,
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(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
return listOf(IAstModification.ReplaceNode(
functionCall, typecast, parent
))
}
return noModifications
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// is it a struct variable? then define all its struct members as mangled names, // is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well. // and include the original decl as well.

View File

@ -52,7 +52,19 @@ interface IAstModification {
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification { class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() { override fun perform() {
if(parent is INameScope) { if(parent is INameScope) {
val idx = parent.statements.withIndex().find { it.value===after }!!.index + 1 val idx = parent.statements.indexOfFirst { it===after } + 1
parent.statements.add(idx, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
}
}
class InsertBefore(val before: Statement, val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
val idx = parent.statements.indexOfFirst { it===before }
parent.statements.add(idx, stmt) parent.statements.add(idx, stmt)
stmt.linkParents(parent) stmt.linkParents(parent)
} else { } else {
@ -70,6 +82,7 @@ interface IAstModification {
class SwapOperands(val expr: BinaryExpression): IAstModification { class SwapOperands(val expr: BinaryExpression): IAstModification {
override fun perform() { override fun perform() {
require(expr.operator in associativeOperators)
val tmp = expr.left val tmp = expr.left
expr.left = expr.right expr.left = expr.right
expr.right = tmp expr.right = tmp
@ -88,13 +101,12 @@ abstract class AstWalker {
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList() open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList() open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList() open fun before(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList() open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList() open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList() open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList() open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList() open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(foreverLoop: ForeverLoop, parent: Node): Iterable<IAstModification> = emptyList() open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList() open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList() open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList() open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
@ -110,13 +122,11 @@ abstract class AstWalker {
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList() open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(program: Program, parent: Node): Iterable<IAstModification> = emptyList() open fun before(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList() open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList() open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList() open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList() open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList() open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList() open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList() open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList() open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList() open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
@ -132,13 +142,12 @@ abstract class AstWalker {
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList() open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList() open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList() open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList() open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList() open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList() open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList() open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList() open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(foreverLoop: ForeverLoop, parent: Node): Iterable<IAstModification> = emptyList() open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList() open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList() open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList() open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
@ -154,13 +163,11 @@ abstract class AstWalker {
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList() open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(program: Program, parent: Node): Iterable<IAstModification> = emptyList() open fun after(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList() open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList() open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList() open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList() open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList() open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList() open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList() open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList() open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList() open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
@ -222,6 +229,7 @@ abstract class AstWalker {
track(before(decl, parent), decl, parent) track(before(decl, parent), decl, parent)
decl.value?.accept(this, decl) decl.value?.accept(this, decl)
decl.arraysize?.accept(this, decl) decl.arraysize?.accept(this, decl)
decl.struct?.accept(this, decl)
track(after(decl, parent), decl, parent) track(after(decl, parent), decl, parent)
} }
@ -313,11 +321,6 @@ abstract class AstWalker {
track(after(postIncrDecr, parent), postIncrDecr, parent) track(after(postIncrDecr, parent), postIncrDecr, parent)
} }
fun visit(contStmt: Continue, parent: Node) {
track(before(contStmt, parent), contStmt, parent)
track(after(contStmt, parent), contStmt, parent)
}
fun visit(breakStmt: Break, parent: Node) { fun visit(breakStmt: Break, parent: Node) {
track(before(breakStmt, parent), breakStmt, parent) track(before(breakStmt, parent), breakStmt, parent)
track(after(breakStmt, parent), breakStmt, parent) track(after(breakStmt, parent), breakStmt, parent)
@ -325,7 +328,7 @@ abstract class AstWalker {
fun visit(forLoop: ForLoop, parent: Node) { fun visit(forLoop: ForLoop, parent: Node) {
track(before(forLoop, parent), forLoop, parent) track(before(forLoop, parent), forLoop, parent)
forLoop.loopVar?.accept(this, forLoop) forLoop.loopVar.accept(this, forLoop)
forLoop.iterable.accept(this, forLoop) forLoop.iterable.accept(this, forLoop)
forLoop.body.accept(this, forLoop) forLoop.body.accept(this, forLoop)
track(after(forLoop, parent), forLoop, parent) track(after(forLoop, parent), forLoop, parent)
@ -338,19 +341,20 @@ abstract class AstWalker {
track(after(whileLoop, parent), whileLoop, parent) track(after(whileLoop, parent), whileLoop, parent)
} }
fun visit(foreverLoop: ForeverLoop, parent: Node) {
track(before(foreverLoop, parent), foreverLoop, parent)
foreverLoop.body.accept(this, foreverLoop)
track(after(foreverLoop, parent), foreverLoop, parent)
}
fun visit(repeatLoop: RepeatLoop, parent: Node) { fun visit(repeatLoop: RepeatLoop, parent: Node) {
track(before(repeatLoop, parent), repeatLoop, parent) track(before(repeatLoop, parent), repeatLoop, parent)
repeatLoop.untilCondition.accept(this, repeatLoop) repeatLoop.iterations?.accept(this, repeatLoop)
repeatLoop.body.accept(this, repeatLoop) repeatLoop.body.accept(this, repeatLoop)
track(after(repeatLoop, parent), repeatLoop, parent) track(after(repeatLoop, parent), repeatLoop, parent)
} }
fun visit(untilLoop: UntilLoop, parent: Node) {
track(before(untilLoop, parent), untilLoop, parent)
untilLoop.condition.accept(this, untilLoop)
untilLoop.body.accept(this, untilLoop)
track(after(untilLoop, parent), untilLoop, parent)
}
fun visit(returnStmt: Return, parent: Node) { fun visit(returnStmt: Return, parent: Node) {
track(before(returnStmt, parent), returnStmt, parent) track(before(returnStmt, parent), returnStmt, parent)
returnStmt.value?.accept(this, returnStmt) returnStmt.value?.accept(this, returnStmt)
@ -407,11 +411,6 @@ abstract class AstWalker {
track(after(inlineAssembly, parent), inlineAssembly, parent) track(after(inlineAssembly, parent), inlineAssembly, parent)
} }
fun visit(registerExpr: RegisterExpr, parent: Node) {
track(before(registerExpr, parent), registerExpr, parent)
track(after(registerExpr, parent), registerExpr, parent)
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) { fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) {
track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent) track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent) track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
@ -441,11 +440,5 @@ abstract class AstWalker {
structDecl.statements.forEach { it.accept(this, structDecl) } structDecl.statements.forEach { it.accept(this, structDecl) }
track(after(structDecl, parent), structDecl, parent) track(after(structDecl, parent), structDecl, parent)
} }
fun visit(structLv: StructLiteralValue, parent: Node) {
track(before(structLv, parent), structLv, parent)
structLv.values.forEach { it.accept(this, structLv) }
track(after(structLv, parent), structLv, parent)
}
} }

View File

@ -33,6 +33,7 @@ interface IAstVisitor {
fun visit(decl: VarDecl) { fun visit(decl: VarDecl) {
decl.value?.accept(this) decl.value?.accept(this)
decl.arraysize?.accept(this) decl.arraysize?.accept(this)
decl.struct?.accept(this)
} }
fun visit(subroutine: Subroutine) { fun visit(subroutine: Subroutine) {
@ -95,14 +96,11 @@ interface IAstVisitor {
postIncrDecr.target.accept(this) postIncrDecr.target.accept(this)
} }
fun visit(contStmt: Continue) {
}
fun visit(breakStmt: Break) { fun visit(breakStmt: Break) {
} }
fun visit(forLoop: ForLoop) { fun visit(forLoop: ForLoop) {
forLoop.loopVar?.accept(this) forLoop.loopVar.accept(this)
forLoop.iterable.accept(this) forLoop.iterable.accept(this)
forLoop.body.accept(this) forLoop.body.accept(this)
} }
@ -112,13 +110,14 @@ interface IAstVisitor {
whileLoop.body.accept(this) whileLoop.body.accept(this)
} }
fun visit(foreverLoop: ForeverLoop) { fun visit(repeatLoop: RepeatLoop) {
foreverLoop.body.accept(this) repeatLoop.iterations?.accept(this)
repeatLoop.body.accept(this)
} }
fun visit(repeatLoop: RepeatLoop) { fun visit(untilLoop: UntilLoop) {
repeatLoop.untilCondition.accept(this) untilLoop.condition.accept(this)
repeatLoop.body.accept(this) untilLoop.body.accept(this)
} }
fun visit(returnStmt: Return) { fun visit(returnStmt: Return) {
@ -159,9 +158,6 @@ interface IAstVisitor {
fun visit(inlineAssembly: InlineAssembly) { fun visit(inlineAssembly: InlineAssembly) {
} }
fun visit(registerExpr: RegisterExpr) {
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) { fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
} }
@ -181,8 +177,4 @@ interface IAstVisitor {
fun visit(structDecl: StructDecl) { fun visit(structDecl: StructDecl) {
structDecl.statements.forEach { it.accept(this) } structDecl.statements.forEach { it.accept(this) }
} }
fun visit(structLv: StructLiteralValue) {
structLv.values.forEach { it.accept(this) }
}
} }

View File

@ -14,8 +14,8 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
// - in every scope, most directives and vardecls are moved to the top. // - in every scope, most directives and vardecls are moved to the top.
// - the 'start' subroutine is moved to the top. // - the 'start' subroutine is moved to the top.
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement. // - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - (syntax desugaring) augmented assignment is turned into regular assignment.
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments. // - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
// - in-place assignments are reordered a bit so that they are mostly of the form A = A <operator> <rest>
// - sorts the choices in when statement. // - sorts the choices in when statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc). // - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
@ -71,23 +71,6 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return noModifications return noModifications
} }
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val declConstValue = declValue.constValue(program)
if(declConstValue==null) {
// move the vardecl (without value) to the scope and replace this with a regular assignment
decl.value = null
val target = AssignTarget(null, IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, null, declValue, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope() as Node)
)
}
}
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> { override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy { val choices = whenStatement.choiceValues(program).sortedBy {
@ -99,15 +82,11 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
} }
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.aug_op!=null) {
return listOf(IAstModification.ReplaceNode(assignment, assignment.asDesugaredNonaugmented(), parent))
}
val valueType = assignment.value.inferType(program) val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment) val targetType = assignment.target.inferType(program, assignment)
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) { if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
val assignments = if (assignment.value is StructLiteralValue) { val assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = { ..... } ' flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = [ ..... ] '
} else { } else {
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2' flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
} }
@ -122,22 +101,71 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
return noModifications return noModifications
} }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// rewrite in-place assignment expressions a bit so that the assignment target usually is the leftmost operand
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null) {
if(binExpr.left isSameAs assignment.target) {
// A = A <operator> 5, unchanged
return noModifications
}
if(binExpr.operator in associativeOperators) {
if (binExpr.right isSameAs assignment.target) {
// A = v <associative-operator> A ==> A = A <associative-operator> v
return listOf(IAstModification.SwapOperands(binExpr))
}
val leftBinExpr = binExpr.left as? BinaryExpression
if(leftBinExpr?.operator == binExpr.operator) {
return if(leftBinExpr.left isSameAs assignment.target) {
// A = (A <associative-operator> x) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(leftBinExpr.right, binExpr.operator, binExpr.right, binExpr.position)
val newValue = BinaryExpression(leftBinExpr.left, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
} else {
// A = (x <associative-operator> A) <same-operator> y ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(leftBinExpr.left, binExpr.operator, binExpr.right, binExpr.position)
val newValue = BinaryExpression(leftBinExpr.right, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
}
}
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr?.operator == binExpr.operator) {
return if(rightBinExpr.left isSameAs assignment.target) {
// A = x <associative-operator> (A <same-operator> y) ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.right, binExpr.position)
val newValue = BinaryExpression(rightBinExpr.left, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
} else {
// A = x <associative-operator> (y <same-operator> A) ==> A = A <associative-operator> (x <same-operator> y)
val newRight = BinaryExpression(binExpr.left, binExpr.operator, rightBinExpr.left, binExpr.position)
val newValue = BinaryExpression(rightBinExpr.right, binExpr.operator, newRight, binExpr.position)
listOf(IAstModification.ReplaceNode(binExpr, newValue, assignment))
}
}
}
}
return noModifications
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> { private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!! val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single() val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!! val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!! val struct = targetVar.struct!!
val slv = structAssignment.value as? StructLiteralValue val slv = structAssignment.value as? ArrayLiteralValue
if(slv==null || slv.values.size != struct.numberOfElements) if(slv==null || slv.value.size != struct.numberOfElements)
throw FatalAstException("element count mismatch") throw FatalAstException("element count mismatch")
return struct.statements.zip(slv.values).map { (targetDecl, sourceValue) -> return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
targetDecl as VarDecl targetDecl as VarDecl
val mangled = mangledStructMemberName(identifierName, targetDecl.name) val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position) val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position), val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
null, sourceValue, sourceValue.position) sourceValue, sourceValue.position)
assign.linkParents(structAssignment) assign.linkParents(structAssignment)
assign assign
} }
@ -151,30 +179,50 @@ internal class StatementReorderer(val program: Program) : AstWalker() {
when (structAssignment.value) { when (structAssignment.value) {
is IdentifierReference -> { is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!! val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null) when {
throw FatalAstException("can only assign arrays or structs to structs") sourceVar.struct!=null -> {
// struct memberwise copy // struct memberwise copy
val sourceStruct = sourceVar.struct!! val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) { if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment // structs are not the same in assignment
return listOf() // error will be printed elsewhere return listOf() // error will be printed elsewhere
} }
return struct.statements.zip(sourceStruct.statements).map { member -> if(struct.statements.size!=sourceStruct.statements.size)
val targetDecl = member.first as VarDecl return listOf() // error will be printed elsewhere
val sourceDecl = member.second as VarDecl return struct.statements.zip(sourceStruct.statements).map { member ->
if(targetDecl.name != sourceDecl.name) val targetDecl = member.first as VarDecl
throw FatalAstException("struct member mismatch") val sourceDecl = member.second as VarDecl
val mangled = mangledStructMemberName(identifierName, targetDecl.name) if(targetDecl.name != sourceDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position) throw FatalAstException("struct member mismatch")
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name) val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position) val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position), val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
null, sourceIdref, member.second.position) val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
assign.linkParents(structAssignment) val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
assign 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 StructLiteralValue -> { is ArrayLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here") throw IllegalArgumentException("not going to flatten a structLv assignment here")
} }
else -> throw FatalAstException("strange struct value") else -> throw FatalAstException("strange struct value")

View File

@ -1,39 +0,0 @@
package prog8.ast.processing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.statements.*
import prog8.optimizer.CallGraph
internal class SubroutineInliner(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
private val callgraph = CallGraph(program)
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(!subroutine.isAsmSubroutine && callgraph.calledBy[subroutine]!=null && subroutine.containsCodeOrVars()) {
// TODO for now, inlined subroutines can't have parameters or local variables - improve this
if(subroutine.parameters.isEmpty() && subroutine.containsNoVars()) {
if (subroutine.countStatements() <= 5) {
if (callgraph.calledBy.getValue(subroutine).size == 1 || !subroutine.statements.any { it.expensiveToInline })
return inline(subroutine)
}
}
}
return noModifications
}
private fun inline(subroutine: Subroutine): Iterable<IAstModification> {
val calls = callgraph.calledBy.getValue(subroutine)
return calls.map {
call -> IAstModification.ReplaceNode(
call,
AnonymousScope(subroutine.statements, call.position),
call.parent
)
}.plus(IAstModification.Remove(subroutine, subroutine.parent))
}
}

View File

@ -18,6 +18,21 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(decl.type==VarDeclType.VAR && declValue!=null && decl.struct==null) {
val valueDt = declValue.inferType(program)
if(!valueDt.istype(decl.datatype)) {
return listOf(IAstModification.ReplaceNode(
declValue,
TypecastExpression(declValue, decl.datatype, true, declValue.position),
decl
))
}
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftDt = expr.left.inferType(program) val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program) val rightDt = expr.right.inferType(program)
@ -51,8 +66,13 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
TypecastExpression(assignment.value, targettype, true, assignment.value.position), TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment)) assignment))
} else { } else {
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> = fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> {
listOf(IAstModification.ReplaceNode(cvalue, cvalue.cast(targettype), cvalue.parent)) val cast = cvalue.cast(targettype)
return if(cast.isValid)
listOf(IAstModification.ReplaceNode(cvalue, cast.valueOrZero(), cvalue.parent))
else
emptyList()
}
val cvalue = assignment.value.constValue(program) val cvalue = assignment.value.constValue(program)
if(cvalue!=null) { if(cvalue!=null) {
val number = cvalue.number.toDouble() val number = cvalue.number.toDouble()
@ -109,15 +129,12 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position), AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node) call as Node)
} else if(arg.second.value is NumericLiteralValue) { } else if(arg.second.value is NumericLiteralValue) {
try { val cast = (arg.second.value as NumericLiteralValue).cast(requiredType)
val castedValue = (arg.second.value as NumericLiteralValue).cast(requiredType) if(cast.isValid)
modifications += IAstModification.ReplaceNode( modifications += IAstModification.ReplaceNode(
call.args[arg.second.index], call.args[arg.second.index],
castedValue, cast.valueOrZero(),
call as Node) call as Node)
} catch (x: ExpressionError) {
// no cast possible
}
} }
} }
} }
@ -137,13 +154,13 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
call.args[arg.second.index], call.args[arg.second.index],
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position), TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
call as Node) call as Node)
break
} }
} }
} }
} }
} }
null -> { } else -> { }
else -> throw FatalAstException("call to something weird $sub ${call.target}")
} }
return modifications return modifications
@ -152,7 +169,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// warn about any implicit type casts to Float, because that may not be intended // warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) { if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
errors.warn("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position) errors.warn("integer implicitly converted to float. Suggestion: use float literals, add an explicit cast, or revert to integer arithmetic", typecast.position)
} }
return noModifications return noModifications
} }
@ -161,7 +178,7 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
// make sure the memory address is an uword // make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program) val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) { if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD) val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position) ?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread)) return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
} }
@ -172,58 +189,13 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
// make sure the memory address is an uword // make sure the memory address is an uword
val dt = memwrite.addressExpression.inferType(program) val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) { if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD) val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position) ?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite)) return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
} }
return noModifications return noModifications
} }
override fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> {
// assignment of a struct literal value, some member values may need proper typecast
fun addTypecastsIfNeeded(struct: StructDecl): Iterable<IAstModification> {
val newValues = struct.statements.zip(structLv.values).map { (structMemberDecl, memberValue) ->
val memberDt = (structMemberDecl as VarDecl).datatype
val valueDt = memberValue.inferType(program)
if (valueDt.typeOrElse(memberDt) != memberDt)
TypecastExpression(memberValue, memberDt, true, memberValue.position)
else
memberValue
}
class StructLvValueReplacer(val targetStructLv: StructLiteralValue, val typecastValues: List<Expression>) : IAstModification {
override fun perform() {
targetStructLv.values = typecastValues
typecastValues.forEach { it.linkParents(targetStructLv) }
}
}
return if(structLv.values.zip(newValues).any { (v1, v2) -> v1 !== v2})
listOf(StructLvValueReplacer(structLv, newValues))
else
emptyList()
}
val decl = structLv.parent as? VarDecl
if(decl != null) {
val struct = decl.struct
if(struct != null)
return addTypecastsIfNeeded(struct)
} else {
val assign = structLv.parent as? Assignment
if (assign != null) {
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
if(decl2 != null) {
val struct = decl2.struct
if(struct != null)
return addTypecastsIfNeeded(struct)
}
}
}
return noModifications
}
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> { override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
// add a typecast to the return type if it doesn't match the subroutine's signature // add a typecast to the return type if it doesn't match the subroutine's signature
val returnValue = returnStmt.value val returnValue = returnStmt.value
@ -234,7 +206,9 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
if (returnValue.inferType(program).istype(subReturnType)) if (returnValue.inferType(program).istype(subReturnType))
return noModifications return noModifications
if (returnValue is NumericLiteralValue) { if (returnValue is NumericLiteralValue) {
returnStmt.value = returnValue.cast(subroutine.returntypes.single()) val cast = returnValue.cast(subroutine.returntypes.single())
if(cast.isValid)
returnStmt.value = cast.valueOrZero()
} else { } else {
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
returnValue, returnValue,

View File

@ -35,7 +35,8 @@ internal class VariousCleanups: AstWalker() {
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) { if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type) val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
return listOf(IAstModification.ReplaceNode(typecast, value, parent)) if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
} }
return noModifications return noModifications

View File

@ -13,30 +13,63 @@ import prog8.functions.BuiltinFunctions
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor { class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall) override fun visit(functionCall: FunctionCall) {
= checkTypes(functionCall as IFunctionCall, functionCall.definingScope()) val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program)
if(error!=null)
throw CompilerException(error)
}
override fun visit(functionCallStatement: FunctionCallStatement) override fun visit(functionCallStatement: FunctionCallStatement) {
= checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope()) val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program)
if (error!=null)
throw CompilerException(error)
}
private fun checkTypes(call: IFunctionCall, scope: INameScope) { companion object {
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope) private fun argTypeCompatible(argDt: DataType, paramDt: DataType): Boolean {
when(target) { if(argDt==paramDt)
is Subroutine -> { 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 paramtypes = target.parameters.map { it.type }
if(argtypes!=paramtypes) val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
throw CompilerException("parameter type mismatch $call") if(mismatch>=0) {
} val actual = argtypes[mismatch].toString()
is BuiltinFunctionStatementPlaceholder -> { val expected = paramtypes[mismatch].toString()
val func = BuiltinFunctions.getValue(target.name) return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
val paramtypes = func.parameters.map { it.possibleDatatypes }
for(x in argtypes.zip(paramtypes)) {
if(x.first !in x.second)
throw CompilerException("parameter type mismatch $call")
} }
} }
else -> {} else if (target is BuiltinFunctionStatementPlaceholder) {
val func = BuiltinFunctions.getValue(target.name)
if(call.args.size != func.parameters.size)
return "invalid number of arguments"
val paramtypes = func.parameters.map { it.possibleDatatypes }
for (x in argtypes.zip(paramtypes).withIndex()) {
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"
}
}
}
return null
} }
} }
} }

View File

@ -5,6 +5,7 @@ import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.compiler.target.CompilationTarget
sealed class Statement : Node { sealed class Statement : Node {
@ -29,8 +30,6 @@ sealed class Statement : Node {
return scope.joinToString(".") return scope.joinToString(".")
} }
abstract val expensiveToInline: Boolean
fun definingBlock(): Block { fun definingBlock(): Block {
if(this is Block) if(this is Block)
return this return this
@ -48,7 +47,6 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this replacement.parent = this
} }
override val expensiveToInline = false
} }
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean) data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
@ -59,8 +57,6 @@ class Block(override val name: String,
val isInLibrary: Boolean, val isInLibrary: Boolean,
override val position: Position) : Statement(), INameScope { override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -69,7 +65,7 @@ class Block(override val name: String,
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement) require(replacement is Statement)
val idx = statements.withIndex().find { it.value===node }!!.index val idx = statements.indexOfFirst { it ===node }
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this replacement.parent = this
} }
@ -86,7 +82,6 @@ class Block(override val name: String,
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() { data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -109,7 +104,6 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
data class Label(val name: String, override val position: Position) : Statement() { data class Label(val name: String, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -126,7 +120,6 @@ data class Label(val name: String, override val position: Position) : Statement(
open class Return(var value: Expression?, override val position: Position) : Statement() { open class Return(var value: Expression?, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = value!=null && value !is NumericLiteralValue
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -147,31 +140,8 @@ open class Return(var value: Expression?, override val position: Position) : Sta
} }
} }
class ReturnFromIrq(override val position: Position) : Return(null, position) {
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String {
return "ReturnFromIrq(pos=$position)"
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
}
class Continue(override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) {
this.parent=parent
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class Break(override val position: Position) : Statement() { class Break(override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent=parent this.parent=parent
@ -207,9 +177,6 @@ open class VarDecl(val type: VarDeclType,
var structHasBeenFlattened = false // set later var structHasBeenFlattened = false // set later
private set private set
override val expensiveToInline
get() = value!=null && value !is NumericLiteralValue
// prefix for literal values that are turned into a variable on the heap // prefix for literal values that are turned into a variable on the heap
companion object { companion object {
@ -248,8 +215,9 @@ open class VarDecl(val type: VarDeclType,
DataType.UWORD -> DataType.ARRAY_UW DataType.UWORD -> DataType.ARRAY_UW
DataType.WORD -> DataType.ARRAY_W DataType.WORD -> DataType.ARRAY_W
DataType.FLOAT -> DataType.ARRAY_F DataType.FLOAT -> DataType.ARRAY_F
DataType.STR -> DataType.ARRAY_UW // use memory address of the string instead
else -> { 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 DataType.ARRAY_UB
} }
} }
@ -283,7 +251,7 @@ open class VarDecl(val type: VarDeclType,
fun flattenStructMembers(): MutableList<Statement> { fun flattenStructMembers(): MutableList<Statement> {
val result = struct!!.statements.withIndex().map { val result = struct!!.statements.withIndex().map {
val member = it.value as VarDecl val member = it.value as VarDecl
val initvalue = if(value!=null) (value as StructLiteralValue).values[it.index] else null val initvalue = if(value!=null) (value as ArrayLiteralValue).value[it.index] else null
VarDecl( VarDecl(
VarDeclType.VAR, VarDeclType.VAR,
member.datatype, member.datatype,
@ -295,8 +263,8 @@ open class VarDecl(val type: VarDeclType,
member.isArray, member.isArray,
true, true,
member.position member.position
) as Statement )
}.toMutableList() }.toMutableList<Statement>()
structHasBeenFlattened = true structHasBeenFlattened = true
return result return result
} }
@ -304,7 +272,7 @@ open class VarDecl(val type: VarDeclType,
// a vardecl used only for subroutine parameters // a vardecl used only for subroutine parameters
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position) class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.NOT_IN_ZEROPAGE, null, name, null, null, false, true, position) : VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.DONTCARE, null, name, null, null, false, true, position)
class ArrayIndex(var index: Expression, override val position: Position) : Node { class ArrayIndex(var index: Expression, override val position: Position) : Node {
@ -334,13 +302,13 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
return("ArrayIndex($index, pos=$position)") return("ArrayIndex($index, pos=$position)")
} }
fun size() = (index as? NumericLiteralValue)?.number?.toInt() fun constIndex() = (index as? NumericLiteralValue)?.number?.toInt()
infix fun isSameAs(other: ArrayIndex) = index.isSameAs(other.index)
} }
open class Assignment(var target: AssignTarget, var aug_op : String?, var value: Expression, override val position: Position) : Statement() { open class Assignment(var target: AssignTarget, var value: Expression, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline
get() = value is BinaryExpression
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -361,36 +329,58 @@ open class Assignment(var target: AssignTarget, var aug_op : String?, var value:
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String { override fun toString(): String {
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)") return("Assignment(target: $target, value: $value, pos=$position)")
} }
fun asDesugaredNonaugmented(): Assignment { /**
val augmented = aug_op ?: return this * Is the assigment value an expression that references the assignment target itself?
* The expression can be a BinaryExpression, PrefixExpression or TypecastExpression (possibly with one sub-cast).
*/
val isAugmentable: Boolean
get() {
val binExpr = value as? BinaryExpression
if(binExpr!=null) {
if(binExpr.left isSameAs target)
return true // A = A <operator> Something
val leftOperand: Expression = if(binExpr.operator in associativeOperators) {
when { if (binExpr.left !is BinaryExpression && binExpr.right isSameAs target)
target.register != null -> RegisterExpr(target.register!!, target.position) return true // A = v <associative-operator> A
target.identifier != null -> target.identifier!!
target.arrayindexed != null -> target.arrayindexed!! val leftBinExpr = binExpr.left as? BinaryExpression
target.memoryAddress != null -> DirectMemoryRead(target.memoryAddress!!.addressExpression, value.position) if(leftBinExpr?.operator == binExpr.operator) {
else -> throw FatalAstException("strange this") // one of these?
// A = (A <associative-operator> x) <same-operator> y
// A = (x <associative-operator> A) <same-operator> y
// A = (x <associative-operator> y) <same-operator> A
return leftBinExpr.left isSameAs target || leftBinExpr.right isSameAs target || binExpr.right isSameAs target
}
val rightBinExpr = binExpr.right as? BinaryExpression
if(rightBinExpr?.operator == binExpr.operator) {
// one of these?
// A = y <associative-operator> (A <same-operator> x)
// A = y <associative-operator> (x <same-operator> y)
// A = A <associative-operator> (x <same-operator> y)
return rightBinExpr.left isSameAs target || rightBinExpr.right isSameAs target || binExpr.left isSameAs target
}
} }
val assignment =
if(augmented=="setvalue") {
Assignment(target, null, value, position)
} else {
val expression = BinaryExpression(leftOperand, augmented.substringBeforeLast('='), value, position)
Assignment(target, null, expression, position)
} }
assignment.linkParents(parent)
return assignment val prefixExpr = value as? PrefixExpression
} if(prefixExpr!=null)
return prefixExpr.expression isSameAs target
val castExpr = value as? TypecastExpression
if(castExpr!=null) {
val subCast = castExpr.expression as? TypecastExpression
return if(subCast!=null) subCast.expression isSameAs target else castExpr.expression isSameAs target
}
return false
}
} }
data class AssignTarget(val register: Register?, data class AssignTarget(var identifier: IdentifierReference?,
var identifier: IdentifierReference?,
var arrayindexed: ArrayIndexedExpression?, var arrayindexed: ArrayIndexedExpression?,
val memoryAddress: DirectMemoryWrite?, val memoryAddress: DirectMemoryWrite?,
override val position: Position) : Node { override val position: Position) : Node {
@ -405,8 +395,8 @@ data class AssignTarget(val register: Register?,
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
when { when {
node===identifier -> identifier = replacement as IdentifierReference node === identifier -> identifier = replacement as IdentifierReference
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression node === arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
else -> throw FatalAstException("invalid replace") else -> throw FatalAstException("invalid replace")
} }
replacement.parent = this replacement.parent = this
@ -418,98 +408,121 @@ data class AssignTarget(val register: Register?,
companion object { companion object {
fun fromExpr(expr: Expression): AssignTarget { fun fromExpr(expr: Expression): AssignTarget {
return when (expr) { return when (expr) {
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position) is IdentifierReference -> AssignTarget(expr, null, null, expr.position)
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position) is ArrayIndexedExpression -> AssignTarget(null, expr, null, expr.position)
is ArrayIndexedExpression -> AssignTarget(null, null, expr, null, expr.position) is DirectMemoryRead -> AssignTarget(null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
is DirectMemoryRead -> AssignTarget(null, null, null, DirectMemoryWrite(expr.addressExpression, expr.position), expr.position)
else -> throw FatalAstException("invalid expression object $expr") else -> throw FatalAstException("invalid expression object $expr")
} }
} }
} }
fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType { fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
if(register!=null) if (identifier != null) {
return InferredTypes.knownFor(DataType.UBYTE)
if(identifier!=null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown() val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype) if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
} }
if(arrayindexed!=null) { if (arrayindexed != null) {
return arrayindexed!!.inferType(program) return arrayindexed!!.inferType(program)
} }
if(memoryAddress!=null) if (memoryAddress != null)
return InferredTypes.knownFor(DataType.UBYTE) return InferredTypes.knownFor(DataType.UBYTE)
return InferredTypes.unknown() return InferredTypes.unknown()
} }
fun toExpression(): Expression {
return when {
identifier != null -> identifier!!
arrayindexed != null -> arrayindexed!!
memoryAddress != null -> DirectMemoryRead(memoryAddress.addressExpression, memoryAddress.position)
else -> throw FatalAstException("invalid assignmenttarget $this")
}
}
infix fun isSameAs(value: Expression): Boolean { infix fun isSameAs(value: Expression): Boolean {
return when { 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 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 this.memoryAddress.addressExpression isSameAs value.addressExpression
else else
false false
} }
this.register!=null -> value is RegisterExpr && value.register==register identifier != null -> value is IdentifierReference && value.nameInSource == identifier!!.nameInSource
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource arrayindexed != null -> {
this.arrayindexed!=null -> value is ArrayIndexedExpression && if(value is ArrayIndexedExpression && value.identifier.nameInSource == arrayindexed!!.identifier.nameInSource)
value.identifier.nameInSource==arrayindexed!!.identifier.nameInSource && arrayindexed!!.arrayspec isSameAs value.arrayspec
value.arrayspec.size()!=null && else
arrayindexed!!.arrayspec.size()!=null && false
value.arrayspec.size()==arrayindexed!!.arrayspec.size() }
else -> false else -> false
} }
} }
fun isSameAs(other: AssignTarget, program: Program): Boolean { fun isSameAs(other: AssignTarget, program: Program): Boolean {
if(this===other) if (this === other)
return true return true
if(this.register!=null && other.register!=null) if (this.identifier != null && other.identifier != null)
return this.register==other.register return this.identifier!!.nameInSource == other.identifier!!.nameInSource
if(this.identifier!=null && other.identifier!=null) if (this.memoryAddress != null && other.memoryAddress != null) {
return this.identifier!!.nameInSource==other.identifier!!.nameInSource
if(this.memoryAddress!=null && other.memoryAddress!=null) {
val addr1 = this.memoryAddress.addressExpression.constValue(program) val addr1 = this.memoryAddress.addressExpression.constValue(program)
val addr2 = other.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 != null && other.arrayindexed != null) {
if(this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) { if (this.arrayindexed!!.identifier.nameInSource == other.arrayindexed!!.identifier.nameInSource) {
val x1 = this.arrayindexed!!.arrayspec.index.constValue(program) val x1 = this.arrayindexed!!.arrayspec.index.constValue(program)
val x2 = other.arrayindexed!!.arrayspec.index.constValue(program) val x2 = other.arrayindexed!!.arrayspec.index.constValue(program)
return x1!=null && x2!=null && x1==x2 return x1 != null && x2 != null && x1 == x2
} }
} }
return false return false
} }
fun isNotMemory(namespace: INameScope): Boolean { fun isInRegularRAM(namespace: INameScope): Boolean {
if(this.register!=null) when {
return true this.memoryAddress != null -> {
if(this.memoryAddress!=null) return when (this.memoryAddress.addressExpression) {
return false is NumericLiteralValue -> {
if(this.arrayindexed!=null) { CompilationTarget.instance.machine.isRegularRAMaddress((this.memoryAddress.addressExpression as NumericLiteralValue).number.toInt())
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace) }
if(targetStmt!=null) is IdentifierReference -> {
return targetStmt.type!= VarDeclType.MEMORY val decl = (this.memoryAddress.addressExpression as IdentifierReference).targetVarDecl(namespace)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
CompilationTarget.instance.machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
this.arrayindexed != null -> {
val targetStmt = this.arrayindexed!!.identifier.targetVarDecl(namespace)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
CompilationTarget.instance.machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
this.identifier != null -> {
val decl = this.identifier!!.targetVarDecl(namespace)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
CompilationTarget.instance.machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
} }
if(this.identifier!=null) {
val targetStmt = this.identifier!!.targetVarDecl(namespace)
if(targetStmt!=null)
return targetStmt.type!= VarDeclType.MEMORY
}
return false
} }
} }
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() { class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -535,7 +548,6 @@ class Jump(val address: Int?,
val generatedLabel: String?, // used in code generation scenarios val generatedLabel: String?, // used in code generation scenarios
override val position: Position) : Statement() { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -556,8 +568,6 @@ class FunctionCallStatement(override var target: IdentifierReference,
val void: Boolean, val void: Boolean,
override val position: Position) : Statement(), IFunctionCall { override val position: Position) : Statement(), IFunctionCall {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline
get() = args.any { it !is NumericLiteralValue }
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -569,7 +579,7 @@ class FunctionCallStatement(override var target: IdentifierReference,
if(node===target) if(node===target)
target = replacement as IdentifierReference target = replacement as IdentifierReference
else { else {
val idx = args.withIndex().find { it.value===node }!!.index val idx = args.indexOfFirst { it===node }
args[idx] = replacement as Expression args[idx] = replacement as Expression
} }
replacement.parent = this replacement.parent = this
@ -585,7 +595,6 @@ class FunctionCallStatement(override var target: IdentifierReference,
class InlineAssembly(val assembly: String, override val position: Position) : Statement() { class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -600,8 +609,6 @@ class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() { override val position: Position) : INameScope, Statement() {
override val name: String override val name: String
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
companion object { companion object {
private var sequenceNumber = 1 private var sequenceNumber = 1
@ -619,7 +626,7 @@ class AnonymousScope(override var statements: MutableList<Statement>,
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement) require(replacement is Statement)
val idx = statements.withIndex().find { it.value===node }!!.index val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this replacement.parent = this
} }
@ -630,7 +637,6 @@ class AnonymousScope(override var statements: MutableList<Statement>,
class NopStatement(override val position: Position): Statement() { class NopStatement(override val position: Position): Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -639,14 +645,6 @@ class NopStatement(override val position: Position): Statement() {
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here") override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
companion object {
fun insteadOf(stmt: Statement): NopStatement {
val nop = NopStatement(stmt.position)
nop.parent = stmt.parent
return nop
}
}
} }
// the subroutine class covers both the normal user-defined subroutines, // the subroutine class covers both the normal user-defined subroutines,
@ -657,18 +655,13 @@ class Subroutine(override val name: String,
val returntypes: List<DataType>, val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>, val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>, val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<Register>, val asmClobbers: Set<CpuRegister>,
val asmAddress: Int?, val asmAddress: Int?,
val isAsmSubroutine: Boolean, val isAsmSubroutine: Boolean,
override var statements: MutableList<Statement>, override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope { override val position: Position) : Statement(), INameScope {
var keepAlways: Boolean = false
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
override lateinit var parent: Node override lateinit var parent: Node
val scopedname: String by lazy { makeScopedName(name) } val scopedname: String by lazy { makeScopedName(name) }
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -679,7 +672,7 @@ class Subroutine(override val name: String,
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement) require(replacement is Statement)
val idx = statements.withIndex().find { it.value===node }!!.index val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this replacement.parent = this
} }
@ -692,38 +685,13 @@ class Subroutine(override val name: String,
} }
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) } fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun regXasParam() = asmParameterRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun amountOfRtsInAsm(): Int = statements fun amountOfRtsInAsm(): Int = statements
.asSequence() .asSequence()
.filter { it is InlineAssembly } .filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly } .map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it } .count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
fun countStatements(): Int {
class StatementCounter: IAstVisitor {
var count = 0
override fun visit(block: Block) {
count += block.statements.size
super.visit(block)
}
override fun visit(subroutine: Subroutine) {
count += subroutine.statements.size
super.visit(subroutine)
}
override fun visit(scope: AnonymousScope) {
count += scope.statements.size
super.visit(scope)
}
}
// the (recursive) number of statements
val counter = StatementCounter()
counter.visit(this)
return counter.count
}
} }
@ -746,8 +714,6 @@ class IfStatement(var condition: Expression,
var elsepart: AnonymousScope, var elsepart: AnonymousScope,
override val position: Position) : Statement() { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -776,8 +742,6 @@ class BranchStatement(var condition: BranchCondition,
var elsepart: AnonymousScope, var elsepart: AnonymousScope,
override val position: Position) : Statement() { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -799,17 +763,15 @@ class BranchStatement(var condition: BranchCondition,
} }
class ForLoop(val loopRegister: Register?, class ForLoop(var loopVar: IdentifierReference,
var loopVar: IdentifierReference?,
var iterable: Expression, var iterable: Expression,
var body: AnonymousScope, var body: AnonymousScope,
override val position: Position) : Statement() { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent=parent this.parent=parent
loopVar?.linkParents(this) loopVar.linkParents(this)
iterable.linkParents(this) iterable.linkParents(this)
body.linkParents(this) body.linkParents(this)
} }
@ -828,21 +790,16 @@ class ForLoop(val loopRegister: Register?,
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String { override fun toString(): String {
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)" return "ForLoop(loopVar: $loopVar, iterable: $iterable, pos=$position)"
} }
fun loopVarDt(program: Program): InferredTypes.InferredType { fun loopVarDt(program: Program) = loopVar.inferType(program)
val lv = loopVar
return if(loopRegister!=null) InferredTypes.InferredType.known(DataType.UBYTE)
else lv?.inferType(program) ?: InferredTypes.InferredType.unknown()
}
} }
class WhileLoop(var condition: Expression, class WhileLoop(var condition: Expression,
var body: AnonymousScope, var body: AnonymousScope,
override val position: Position) : Statement() { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -863,18 +820,21 @@ class WhileLoop(var condition: Expression,
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
class ForeverLoop(var body: AnonymousScope, override val position: Position) : Statement() { class RepeatLoop(var iterations: Expression?, var body: AnonymousScope, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
iterations?.linkParents(this)
body.linkParents(this) body.linkParents(this)
} }
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===body) when {
body = replacement node===iterations -> iterations = replacement as Expression
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this replacement.parent = this
} }
@ -882,21 +842,20 @@ class ForeverLoop(var body: AnonymousScope, override val position: Position) : S
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
} }
class RepeatLoop(var body: AnonymousScope, class UntilLoop(var body: AnonymousScope,
var untilCondition: Expression, var condition: Expression,
override val position: Position) : Statement() { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
untilCondition.linkParents(this) condition.linkParents(this)
body.linkParents(this) body.linkParents(this)
} }
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
when { when {
node===untilCondition -> untilCondition = replacement as Expression node===condition -> condition = replacement as Expression
node===body -> body = replacement as AnonymousScope node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace") else -> throw FatalAstException("invalid replace")
} }
@ -911,7 +870,6 @@ class WhenStatement(var condition: Expression,
var choices: MutableList<WhenChoice>, var choices: MutableList<WhenChoice>,
override val position: Position): Statement() { override val position: Position): Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline: Boolean = true
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -981,7 +939,6 @@ class StructDecl(override val name: String,
override val position: Position): Statement(), INameScope { override val position: Position): Statement(), INameScope {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline: Boolean = true
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -990,7 +947,7 @@ class StructDecl(override val name: String,
override fun replaceChildNode(node: Node, replacement: Node) { override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement) require(replacement is Statement)
val idx = statements.withIndex().find { it.value===node }!!.index val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement statements[idx] = replacement
replacement.parent = this replacement.parent = this
} }

View File

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

View File

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

View File

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

View File

@ -8,6 +8,12 @@ class ZeropageDepletedError(message: String) : Exception(message)
abstract class Zeropage(protected val options: CompilationOptions) { abstract class Zeropage(protected val options: CompilationOptions) {
abstract val SCRATCH_B1 : Int // temp storage for a single byte
abstract val SCRATCH_REG : Int // temp storage for a register
abstract val SCRATCH_W1 : Int // temp storage 1 for a word $fb+$fc
abstract val SCRATCH_W2 : Int // temp storage 2 for a word $fb+$fc
private val allocations = mutableMapOf<Int, Pair<String, DataType>>() private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations. val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
@ -16,7 +22,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int { fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"} assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE) if(options.zeropage==ZeropageType.DONTUSE)
throw CompilerException("zero page usage has been disabled") throw CompilerException("zero page usage has been disabled")
@ -39,13 +45,13 @@ abstract class Zeropage(protected val options: CompilationOptions) {
if(free.size > 0) { if(free.size > 0) {
if(size==1) { if(size==1) {
for(candidate in free.min()!! .. free.max()!!+1) { for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
if(loneByte(candidate)) if(loneByte(candidate))
return makeAllocation(candidate, 1, datatype, scopedname) return makeAllocation(candidate, 1, datatype, scopedname)
} }
return makeAllocation(free[0], 1, datatype, scopedname) return makeAllocation(free[0], 1, datatype, scopedname)
} }
for(candidate in free.min()!! .. free.max()!!+1) { for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
if (sequentialFree(candidate, size)) if (sequentialFree(candidate, size))
return makeAllocation(candidate, size, datatype, scopedname) return makeAllocation(candidate, size, datatype, scopedname)
} }
@ -64,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 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()) private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
enum class ExitProgramStrategy {
CLEAN_EXIT,
SYSTEM_RESET
}
abstract val exitProgramStrategy: ExitProgramStrategy
} }

View File

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

View File

@ -1,15 +1,39 @@
package prog8.compiler.target package prog8.compiler.target
import prog8.ast.Program
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage import prog8.compiler.Zeropage
import prog8.parser.ModuleImporter
interface IMachineDefinition { internal interface IMachineFloat {
fun toDouble(): Double
fun makeFloatFillAsm(): String
}
internal enum class CpuType {
CPU6502,
CPU65c02
}
internal interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int val FLOAT_MEM_SIZE: Int
val POINTER_MEM_SIZE: Int
val ESTACK_LO: Int
val ESTACK_HI: Int
val BASIC_LOAD_ADDRESS : Int
val RAW_LOAD_ADDRESS : Int
val opcodeNames: Set<String> val opcodeNames: Set<String>
var zeropage: Zeropage
val cpu: CpuType
fun getZeropage(compilerOptions: CompilationOptions): Zeropage fun initializeZeropage(compilerOptions: CompilationOptions)
fun getFloat(num: Number): IMachineFloat
fun getFloatRomConst(number: Double): String?
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
fun launchEmulator(programName: String)
fun isRegularRAMaddress(address: Int): Boolean
} }

View File

@ -2,6 +2,7 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType import prog8.compiler.OutputType
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.IAssemblyProgram import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path import java.nio.file.Path
@ -14,20 +15,20 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list") private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
override fun assemble(options: CompilationOptions) { override fun assemble(options: CompilationOptions) {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps // add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch", "-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor") "--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
val outFile = when (options.output) { val outFile = when (options.output) {
OutputType.PRG -> { OutputType.PRG -> {
command.add("--cbm-prg") command.add("--cbm-prg")
println("\nCreating C-64 prg.") println("\nCreating prg for target ${CompilationTarget.instance.name}.")
prgFile prgFile
} }
OutputType.RAW -> { OutputType.RAW -> {
command.add("--nostart") command.add("--nostart")
println("\nCreating raw binary.") println("\nCreating raw binary for target ${CompilationTarget.instance.name}.")
binFile binFile
} }
} }

View File

@ -1,37 +1,99 @@
package prog8.compiler.target.c64 package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions import prog8.ast.Program
import prog8.compiler.CompilerException import prog8.compiler.*
import prog8.compiler.Zeropage import prog8.compiler.target.CpuType
import prog8.compiler.ZeropageType
import prog8.compiler.target.IMachineDefinition import prog8.compiler.target.IMachineDefinition
import java.awt.Color import prog8.compiler.target.IMachineFloat
import java.awt.image.BufferedImage import prog8.parser.ModuleImporter
import javax.imageio.ImageIO import java.io.IOException
import java.math.RoundingMode
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.pow import kotlin.math.pow
object C64MachineDefinition: IMachineDefinition { internal object C64MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
// 5-byte cbm MFLPT format limitations: // 5-byte cbm MFLPT format limitations:
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255 override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255 override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MEM_SIZE = 5 override val FLOAT_MEM_SIZE = 5
const val BASIC_LOAD_ADDRESS = 0x0801 override val POINTER_MEM_SIZE = 2
const val RAW_LOAD_ADDRESS = 0xc000 override val BASIC_LOAD_ADDRESS = 0x0801
override val RAW_LOAD_ADDRESS = 0xc000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above // and some heavily used string constants derived from the two values above
const val ESTACK_LO_VALUE = 0xce00 // $ce00-$ceff inclusive override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
const val ESTACK_HI_VALUE = 0xcf00 // $cf00-$cfff inclusive override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
const val ESTACK_LO_HEX = "\$ce00"
const val ESTACK_LO_PLUS1_HEX = "\$ce01"
const val ESTACK_LO_PLUS2_HEX = "\$ce02"
const val ESTACK_HI_HEX = "\$cf00"
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
const val ESTACK_HI_PLUS2_HEX = "\$cf02"
override fun getZeropage(compilerOptions: CompilationOptions) = C64Zeropage(compilerOptions) override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun getFloatRomConst(number: Double): String? {
// try to match the ROM float constants to save memory
val mflpt5 = Mflpt5.fromNumber(number)
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
when {
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "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 "floats.FL_PIVAL"
1.4142135624 -> return "floats.FL_SQRTWO"
0.7071067812 -> return "floats.FL_SQRHLF"
0.6931471806 -> return "floats.FL_LOG2"
else -> {}
}
}
}
return null
}
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib")
}
override fun launchEmulator(programName: String) {
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "$programName.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", programName + ".prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(x: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
}
override fun isRegularRAMaddress(address: Int): Boolean = address<0xa000 || address in 0xc000..0xcfff
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
}
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names // 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs", override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
@ -45,20 +107,12 @@ object C64MachineDefinition: IMachineDefinition {
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa") "sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
class C64Zeropage(options: CompilationOptions) : Zeropage(options) { internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
companion object { override val SCRATCH_B1 = 0x02 // temp storage for a single byte
const val SCRATCH_B1 = 0x02 override val SCRATCH_REG = 0x03 // temp storage for a register
const val SCRATCH_REG = 0x03 // temp storage for a register override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer) override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
const val SCRATCH_W1 = 0xfb // $fb+$fc
const val SCRATCH_W2 = 0xfd // $fd+$fe
}
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init { init {
@ -68,7 +122,7 @@ object C64MachineDefinition: IMachineDefinition {
if (options.zeropage == ZeropageType.FULL) { if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9) free.addAll(0x04..0xf9)
free.add(0xff) free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1)) free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else { } else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) { if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
@ -90,7 +144,7 @@ object C64MachineDefinition: IMachineDefinition {
if (options.zeropage == ZeropageType.FLOATSAFE) { if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list // remove the zero page locations used for floating point operations from the free list
free.removeAll(listOf( 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, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
@ -109,19 +163,17 @@ object C64MachineDefinition: IMachineDefinition {
free.clear() free.clear()
} }
} }
assert(SCRATCH_B1 !in free) require(SCRATCH_B1 !in free)
assert(SCRATCH_REG !in free) require(SCRATCH_REG !in free)
assert(SCRATCH_REG_X !in free) require(SCRATCH_W1 !in free)
assert(SCRATCH_W1 !in free) require(SCRATCH_W2 !in free)
assert(SCRATCH_W2 !in free)
for (reserved in options.zpReserved) for (reserved in options.zpReserved)
reserve(reserved) reserve(reserved)
} }
} }
internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat {
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
companion object { companion object {
val zero = Mflpt5(0, 0, 0, 0, 0) val zero = Mflpt5(0, 0, 0, 0, 0)
@ -168,7 +220,7 @@ object C64MachineDefinition: IMachineDefinition {
} }
} }
fun toDouble(): Double { override fun toDouble(): Double {
if (this == zero) return 0.0 if (this == zero) return 0.0
val exp = b0 - 128 val exp = b0 - 128
val sign = (b1.toInt() and 0x80) > 0 val sign = (b1.toInt() and 0x80) > 0
@ -176,91 +228,14 @@ object C64MachineDefinition: IMachineDefinition {
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000 val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
return if (sign) -result else result return if (sign) -result else result
} }
}
object Charset { override fun makeFloatFillAsm(): String {
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png")) val b0 = "$" + b0.toString(16).padStart(2, '0')
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png")) val b1 = "$" + b1.toString(16).padStart(2, '0')
val b2 = "$" + b2.toString(16).padStart(2, '0')
private fun scanChars(img: BufferedImage): Array<BufferedImage> { val b3 = "$" + b3.toString(16).padStart(2, '0')
val b4 = "$" + b4.toString(16).padStart(2, '0')
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB) return "$b0, $b1, $b2, $b3, $b4"
transparent.createGraphics().drawImage(img, 0, 0, null)
val black = Color(0, 0, 0).rgb
val nopixel = Color(0, 0, 0, 0).rgb
for (y in 0 until transparent.height) {
for (x in 0 until transparent.width) {
val col = transparent.getRGB(x, y)
if (col == black)
transparent.setRGB(x, y, nopixel)
}
}
val numColumns = transparent.width / 8
val charImages = (0..255).map {
val charX = it % numColumns
val charY = it / numColumns
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
}
return charImages.toTypedArray()
} }
val normalChars = scanChars(normalImg)
val shiftedChars = scanChars(shiftedImg)
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
val colorIdx = (color % colorPalette.size).toShort()
val chars = coloredNormalChars[colorIdx]
if (chars != null)
return chars[screenCode.toInt()]
val coloredChars = mutableListOf<BufferedImage>()
val transparent = Color(0, 0, 0, 0).rgb
val rgb = colorPalette[colorIdx.toInt()].rgb
for (c in normalChars) {
val colored = c.copy()
for (y in 0 until colored.height)
for (x in 0 until colored.width) {
if (colored.getRGB(x, y) != transparent) {
colored.setRGB(x, y, rgb)
}
}
coloredChars.add(colored)
}
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
}
} }
private fun BufferedImage.copy(): BufferedImage {
val bcopy = BufferedImage(this.width, this.height, this.type)
val g = bcopy.graphics
g.drawImage(this, 0, 0, null)
g.dispose()
return bcopy
}
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
)
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,5 @@
package prog8.compiler.target.c64.codegen package prog8.compiler.target.c64.codegen
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations // note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
@ -87,10 +84,10 @@ private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>):
// the repeated lda can be removed // the repeated lda can be removed
val mods = mutableListOf<Modification>() val mods = mutableListOf<Modification>()
for(lines in linesByFour) { for(lines in linesByFour) {
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" && if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
lines[1].value.trim().startsWith("cmp ") && lines[1].value.trim().startsWith("cmp ") &&
lines[2].value.trim().startsWith("beq ") && lines[2].value.trim().startsWith("beq ") &&
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") { lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
mods.add(Modification(lines[3].index, true, null)) // remove the second lda mods.add(Modification(lines[3].index, true, null)) // remove the second lda
} }
} }
@ -102,10 +99,10 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
// this is a lot harder for word values because the instruction sequence varies. // this is a lot harder for word values because the instruction sequence varies.
val mods = mutableListOf<Modification>() val mods = mutableListOf<Modification>()
for(lines in linesByFour) { for(lines in linesByFour) {
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" && if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
lines[1].value.trim()=="dex" && lines[1].value.trim()=="dex" &&
lines[2].value.trim()=="inx" && lines[2].value.trim()=="inx" &&
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") { lines[3].value.trim()=="lda P8ESTACK_LO,x") {
mods.add(Modification(lines[1].index, true, null)) mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null)) mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null)) mods.add(Modification(lines[3].index, true, null))
@ -154,7 +151,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
} }
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") && if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("jsr c64flt.copy_float")) { fifth.startsWith("lda") && sixth.startsWith("ldy") &&
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
val nineth = pair[8].value.trimStart() val nineth = pair[8].value.trimStart()
val tenth = pair[9].value.trimStart() val tenth = pair[9].value.trimStart()
@ -164,7 +162,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val fourteenth = pair[13].value.trimStart() val fourteenth = pair[13].value.trimStart()
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") && if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") && fourteenth.startsWith("jsr c64flt.copy_float")) { twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) { if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
// identical float init // identical float init
@ -180,6 +179,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
} }
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> { 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 // sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
val mods = mutableListOf<Modification>() val mods = mutableListOf<Modification>()
for (pair in linesByFour) { for (pair in linesByFour) {

View File

@ -4,13 +4,14 @@ import prog8.ast.IFunctionCall
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.FunctionCallStatement
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX import prog8.compiler.target.c64.codegen.assignment.SourceStorageKind
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
import prog8.compiler.toHex import prog8.compiler.toHex
import prog8.functions.FSignature import prog8.functions.FSignature
@ -35,6 +36,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (functionName) { when (functionName) {
"msb" -> funcMsb(fcall) "msb" -> funcMsb(fcall)
"lsb" -> funcLsb(fcall)
"mkword" -> funcMkword(fcall, func) "mkword" -> funcMkword(fcall, func)
"abs" -> funcAbs(fcall, func) "abs" -> funcAbs(fcall, func)
"swap" -> funcSwap(fcall) "swap" -> funcSwap(fcall)
@ -46,8 +48,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"ln", "log2", "sqrt", "rad", "ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil", "deg", "round", "floor", "ceil",
"rdnf" -> funcVariousFloatFuncs(fcall, func, functionName) "rdnf" -> funcVariousFloatFuncs(fcall, func, functionName)
"lsl" -> funcLsl(fcall)
"lsr" -> funcLsr(fcall)
"rol" -> funcRol(fcall) "rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall) "rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall) "ror" -> funcRor(fcall)
@ -57,7 +57,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"rsave" -> { "rsave" -> {
// save cpu status flag and all registers A, X, Y. // save cpu status flag and all registers A, X, Y.
// see http://6502.org/tutorials/register_preservation.html // see http://6502.org/tutorials/register_preservation.html
asmgen.out(" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}") asmgen.out(" php | sta P8ZP_SCRATCH_REG | pha | txa | pha | tya | pha | lda P8ZP_SCRATCH_REG")
} }
"rrestore" -> { "rrestore" -> {
// restore all registers and cpu status flag // restore all registers and cpu status flag
@ -78,15 +78,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val variable = fcall.args.single() val variable = fcall.args.single()
if (variable is IdentifierReference) { if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!! val decl = variable.targetVarDecl(program.namespace)!!
val varName = asmgen.asmIdentifierName(variable) val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.size() val numElements = decl.arraysize!!.constIndex()
when (decl.datatype) { when (decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> { DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out(""" asmgen.out("""
lda #<$varName lda #<$varName
ldy #>$varName ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1} sta P8ZP_SCRATCH_W1
sty ${C64Zeropage.SCRATCH_W1 + 1} sty P8ZP_SCRATCH_W1+1
lda #$numElements lda #$numElements
jsr prog8_lib.reverse_b jsr prog8_lib.reverse_b
""") """)
@ -95,8 +95,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(""" asmgen.out("""
lda #<$varName lda #<$varName
ldy #>$varName ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1} sta P8ZP_SCRATCH_W1
sty ${C64Zeropage.SCRATCH_W1 + 1} sty P8ZP_SCRATCH_W1+1
lda #$numElements lda #$numElements
jsr prog8_lib.reverse_w jsr prog8_lib.reverse_w
""") """)
@ -105,8 +105,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(""" asmgen.out("""
lda #<$varName lda #<$varName
ldy #>$varName ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1} sta P8ZP_SCRATCH_W1
sty ${C64Zeropage.SCRATCH_W1 + 1} sty P8ZP_SCRATCH_W1+1
lda #$numElements lda #$numElements
jsr prog8_lib.reverse_f jsr prog8_lib.reverse_f
""") """)
@ -120,17 +120,17 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val variable = fcall.args.single() val variable = fcall.args.single()
if (variable is IdentifierReference) { if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!! val decl = variable.targetVarDecl(program.namespace)!!
val varName = asmgen.asmIdentifierName(variable) val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.size() val numElements = decl.arraysize!!.constIndex()
when (decl.datatype) { when (decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> { DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out(""" asmgen.out("""
lda #<$varName lda #<$varName
ldy #>$varName ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1} sta P8ZP_SCRATCH_W1
sty ${C64Zeropage.SCRATCH_W1 + 1} sty P8ZP_SCRATCH_W1+1
lda #$numElements lda #$numElements
sta ${C64Zeropage.SCRATCH_B1} sta P8ZP_SCRATCH_B1
""") """)
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b") asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
} }
@ -138,10 +138,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(""" asmgen.out("""
lda #<$varName lda #<$varName
ldy #>$varName ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1} sta P8ZP_SCRATCH_W1
sty ${C64Zeropage.SCRATCH_W1 + 1} sty P8ZP_SCRATCH_W1+1
lda #$numElements lda #$numElements
sta ${C64Zeropage.SCRATCH_B1} sta P8ZP_SCRATCH_B1
""") """)
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w") asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
} }
@ -172,15 +172,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.ror2_mem_ub") asmgen.out(" jsr prog8_lib.ror2_mem_ub")
} }
} }
is RegisterExpr -> {
when (what.register) {
Register.A -> asmgen.out(" lsr a | bcc + | ora #\$80 |+ ")
Register.X -> asmgen.out(" txa | lsr a | bcc + | ora #\$80 |+ tax ")
Register.Y -> asmgen.out(" tya | lsr a | bcc + | ora #\$80 |+ tay ")
}
}
is IdentifierReference -> { is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable") asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
@ -194,7 +187,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.ror2_array_uw") asmgen.out(" jsr prog8_lib.ror2_array_uw")
} }
is IdentifierReference -> { is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ") asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
@ -223,23 +216,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.translateExpression(what.addressExpression) asmgen.translateExpression(what.addressExpression)
asmgen.out(""" asmgen.out("""
inx inx
lda $ESTACK_LO_HEX,x lda P8ESTACK_LO,x
sta (+) + 1 sta (+) + 1
lda $ESTACK_HI_HEX,x lda P8ESTACK_HI,x
sta (+) + 2 sta (+) + 2
+ ror ${'$'}ffff ; modified + ror ${'$'}ffff ; modified
""") """)
} }
} }
is RegisterExpr -> {
when (what.register) {
Register.A -> asmgen.out(" ror a")
Register.X -> asmgen.out(" txa | ror a | tax")
Register.Y -> asmgen.out(" tya | ror a | tay")
}
}
is IdentifierReference -> { is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable") asmgen.out(" ror $variable")
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
@ -253,7 +239,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.ror_array_uw") asmgen.out(" jsr prog8_lib.ror_array_uw")
} }
is IdentifierReference -> { is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable+1 | ror $variable") asmgen.out(" ror $variable+1 | ror $variable")
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
@ -283,15 +269,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.rol2_mem_ub") asmgen.out(" jsr prog8_lib.rol2_mem_ub")
} }
} }
is RegisterExpr -> {
when (what.register) {
Register.A -> asmgen.out(" cmp #\$80 | rol a ")
Register.X -> asmgen.out(" txa | cmp #\$80 | rol a | tax")
Register.Y -> asmgen.out(" tya | cmp #\$80 | rol a | tay")
}
}
is IdentifierReference -> { is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable") asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
@ -305,7 +284,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.rol2_array_uw") asmgen.out(" jsr prog8_lib.rol2_array_uw")
} }
is IdentifierReference -> { is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ") asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
@ -334,23 +313,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.translateExpression(what.addressExpression) asmgen.translateExpression(what.addressExpression)
asmgen.out(""" asmgen.out("""
inx inx
lda $ESTACK_LO_HEX,x lda P8ESTACK_LO,x
sta (+) + 1 sta (+) + 1
lda $ESTACK_HI_HEX,x lda P8ESTACK_HI,x
sta (+) + 2 sta (+) + 2
+ rol ${'$'}ffff ; modified + rol ${'$'}ffff ; modified
""") """)
} }
} }
is RegisterExpr -> {
when (what.register) {
Register.A -> asmgen.out(" rol a")
Register.X -> asmgen.out(" txa | rol a | tax")
Register.Y -> asmgen.out(" tya | rol a | tay")
}
}
is IdentifierReference -> { is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable") asmgen.out(" rol $variable")
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
@ -364,7 +336,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.rol_array_uw") asmgen.out(" jsr prog8_lib.rol_array_uw")
} }
is IdentifierReference -> { is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what) val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable | rol $variable+1") asmgen.out(" rol $variable | rol $variable+1")
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
@ -374,149 +346,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} }
private fun funcLsr(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is RegisterExpr -> {
when (what.register) {
Register.A -> asmgen.out(" lsr a")
Register.X -> asmgen.out(" txa | lsr a | tax")
Register.Y -> asmgen.out(" tya | lsr a | tay")
}
}
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" lsr ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
+ lsr ${'$'}ffff ; modified
""")
}
}
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_ub")
}
else -> throw AssemblyError("weird type")
}
}
DataType.BYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_b")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable | asl a | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lsr $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.WORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_w")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable+1 | asl a | ror $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcLsl(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> {
when (what) {
is RegisterExpr -> {
when (what.register) {
Register.A -> asmgen.out(" asl a")
Register.X -> asmgen.out(" txa | asl a | tax")
Register.Y -> asmgen.out(" tya | asl a | tay")
}
}
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" asl ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
+ asl ${'$'}ffff ; modified
""")
}
}
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsl_array_b")
}
else -> throw AssemblyError("weird type")
}
}
in WordDatatypes -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsl_array_w")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" asl $variable | rol $variable+1")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) { private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) {
translateFunctionArguments(fcall.args, func) translateFunctionArguments(fcall.args, func)
asmgen.out(" jsr c64flt.func_$functionName") asmgen.out(" jsr floats.func_$functionName")
} }
private fun funcSgn(fcall: IFunctionCall, func: FSignature) { private fun funcSgn(fcall: IFunctionCall, func: FSignature) {
@ -527,7 +359,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.BYTE -> asmgen.out(" jsr math.sign_b") DataType.BYTE -> asmgen.out(" jsr math.sign_b")
DataType.UWORD -> asmgen.out(" jsr math.sign_uw") DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
DataType.WORD -> asmgen.out(" jsr math.sign_w") 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") else -> throw AssemblyError("weird type $dt")
} }
} }
@ -538,7 +370,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt.typeOrElse(DataType.STRUCT)) { when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b") 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_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") else -> throw AssemblyError("weird type $dt")
} }
} }
@ -551,25 +383,32 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b") DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw") DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w") 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") else -> throw AssemblyError("weird type $dt")
} }
} }
private fun funcStrlen(fcall: IFunctionCall) { private fun funcStrlen(fcall: IFunctionCall) {
outputPushAddressOfIdentifier(fcall.args[0]) val name = asmgen.asmVariableName(fcall.args[0] as IdentifierReference)
asmgen.out(" jsr prog8_lib.func_strlen") asmgen.out("""
lda #<$name
ldy #>$name
jsr prog8_lib.strlen
sta P8ESTACK_LO,x
dex""")
} }
private fun funcSwap(fcall: IFunctionCall) { private fun funcSwap(fcall: IFunctionCall) {
val first = fcall.args[0] val first = fcall.args[0]
val second = fcall.args[1] val second = fcall.args[1]
// optimized simple case: swap two variables
if(first is IdentifierReference && second is IdentifierReference) { if(first is IdentifierReference && second is IdentifierReference) {
val firstName = asmgen.asmIdentifierName(first) val firstName = asmgen.asmVariableName(first)
val secondName = asmgen.asmIdentifierName(second) val secondName = asmgen.asmVariableName(second)
val dt = first.inferType(program) val dt = first.inferType(program)
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) { 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 return
} }
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) { if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
@ -588,21 +427,203 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(dt.istype(DataType.FLOAT)) { if(dt.istype(DataType.FLOAT)) {
asmgen.out(""" asmgen.out("""
lda #<$firstName lda #<$firstName
sta ${C64Zeropage.SCRATCH_W1} sta P8ZP_SCRATCH_W1
lda #>$firstName lda #>$firstName
sta ${C64Zeropage.SCRATCH_W1+1} sta P8ZP_SCRATCH_W1+1
lda #<$secondName lda #<$secondName
sta ${C64Zeropage.SCRATCH_W2} sta P8ZP_SCRATCH_W2
lda #>$secondName lda #>$secondName
sta ${C64Zeropage.SCRATCH_W2+1} sta P8ZP_SCRATCH_W2+1
jsr c64flt.swap_floats jsr floats.swap_floats
""") """)
return return
} }
} }
// other types of swap() calls should have been replaced by a different statement sequence involving a temp variable // optimized simple case: swap two memory locations
throw AssemblyError("no asm generation for swap funccall $fcall") if(first is DirectMemoryRead && second is DirectMemoryRead) {
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
val name2 = if(second.addressExpression is IdentifierReference) asmgen.asmVariableName(second.addressExpression as IdentifierReference) else null
when {
addr1!=null && addr2!=null -> {
asmgen.out(" ldy $addr1 | lda $addr2 | sta $addr1 | sty $addr2")
return
}
addr1!=null && name2!=null -> {
asmgen.out(" ldy $addr1 | lda $name2 | sta $addr1 | sty $name2")
return
}
name1!=null && addr2 != null -> {
asmgen.out(" ldy $name1 | lda $addr2 | sta $name1 | sty $addr2")
return
}
name1!=null && name2!=null -> {
asmgen.out(" ldy $name1 | lda $name2 | sta $name1 | sty $name2")
return
}
}
}
if(first is ArrayIndexedExpression && second is ArrayIndexedExpression) {
val indexValue1 = first.arrayspec.index as? NumericLiteralValue
val indexName1 = first.arrayspec.index as? IdentifierReference
val indexValue2 = second.arrayspec.index as? NumericLiteralValue
val indexName2 = second.arrayspec.index as? IdentifierReference
val arrayVarName1 = asmgen.asmVariableName(first.identifier)
val arrayVarName2 = asmgen.asmVariableName(second.identifier)
val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT)
if(indexValue1!=null && indexValue2!=null) {
swapArrayValues(elementDt, arrayVarName1, indexValue1, arrayVarName2, indexValue2)
return
} else if(indexName1!=null && indexName2!=null) {
swapArrayValues(elementDt, arrayVarName1, indexName1, arrayVarName2, indexName2)
return
}
}
// all other types of swap() calls are done via the evaluation stack
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
return when (expr) {
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, variable=expr)
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, array = expr)
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, memory = DirectMemoryWrite(expr.addressExpression, expr.position))
else -> throw AssemblyError("invalid expression object $expr")
}
}
asmgen.translateExpression(first)
asmgen.translateExpression(second)
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, datatype),
targetFromExpr(first, datatype),
false, first.position
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, datatype),
targetFromExpr(second, datatype),
false, second.position
)
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val index1 = indexValue1.number.toInt() * elementDt.memorySize()
val index2 = indexValue2.number.toInt() * elementDt.memorySize()
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
lda $arrayVarName1+$index1
ldy $arrayVarName2+$index2
sta $arrayVarName2+$index2
sty $arrayVarName1+$index1
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
lda $arrayVarName1+$index1
ldy $arrayVarName2+$index2
sta $arrayVarName2+$index2
sty $arrayVarName1+$index1
lda $arrayVarName1+$index1+1
ldy $arrayVarName2+$index2+1
sta $arrayVarName2+$index2+1
sty $arrayVarName1+$index1+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1
lda #>(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1+1
lda #<(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2
lda #>(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2+1
jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexName2: IdentifierReference) {
val idxAsmName1 = asmgen.asmVariableName(indexName1)
val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
ldx $idxAsmName1
ldy $idxAsmName2
lda $arrayVarName1,x
pha
lda $arrayVarName2,y
sta $arrayVarName1,x
pla
sta $arrayVarName2,y
ldx P8ZP_SCRATCH_REG
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
lda $idxAsmName1
asl a
tax
lda $idxAsmName2
asl a
tay
lda $arrayVarName1,x
pha
lda $arrayVarName2,y
sta $arrayVarName1,x
pla
sta $arrayVarName2,y
lda $arrayVarName1+1,x
pha
lda $arrayVarName2+1,y
sta $arrayVarName1+1,x
pla
sta $arrayVarName2+1,y
ldx P8ZP_SCRATCH_REG
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #>$arrayVarName1
sta P8ZP_SCRATCH_W1+1
lda $idxAsmName1
asl a
asl a
clc
adc $idxAsmName1
adc #<$arrayVarName1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ lda #>$arrayVarName2
sta P8ZP_SCRATCH_W2+1
lda $idxAsmName2
asl a
asl a
clc
adc $idxAsmName2
adc #<$arrayVarName2
sta P8ZP_SCRATCH_W2
bcc +
inc P8ZP_SCRATCH_W2+1
+ jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
} }
private fun funcAbs(fcall: IFunctionCall, func: FSignature) { private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
@ -611,14 +632,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
when (dt.typeOrElse(DataType.STRUCT)) { when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b") in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w") 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") else -> throw AssemblyError("weird type")
} }
} }
private fun funcMkword(fcall: IFunctionCall, func: FSignature) { private fun funcMkword(fcall: IFunctionCall, func: FSignature) {
translateFunctionArguments(fcall.args, func) // trick: push the args in reverse order (msb first, lsb second) this saves some instructions
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x") asmgen.translateExpression(fcall.args[1])
asmgen.translateExpression(fcall.args[0])
asmgen.out(" inx | lda P8ESTACK_LO,x | sta P8ESTACK_HI+1,x")
} }
private fun funcMsb(fcall: IFunctionCall) { private fun funcMsb(fcall: IFunctionCall) {
@ -626,39 +649,43 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes) if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("msb required word argument") throw AssemblyError("msb required word argument")
if (arg is NumericLiteralValue) if (arg is NumericLiteralValue)
throw AssemblyError("should have been const-folded") throw AssemblyError("msb(const) should have been const-folded away")
if (arg is IdentifierReference) { if (arg is IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(arg) val sourceName = asmgen.asmVariableName(arg)
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex") asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
} else { } else {
asmgen.translateExpression(arg) asmgen.translateExpression(arg)
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x") asmgen.out(" lda P8ESTACK_HI+1,x | sta P8ESTACK_LO+1,x")
}
}
private fun funcLsb(fcall: IFunctionCall) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("lsb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("lsb(const) should have been const-folded away")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg)
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
} else {
asmgen.translateExpression(arg)
// just ignore any high-byte
} }
} }
private fun outputPushAddressAndLenghtOfArray(arg: Expression) { private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
arg as IdentifierReference arg as IdentifierReference
val identifierName = asmgen.asmIdentifierName(arg) val identifierName = asmgen.asmVariableName(arg)
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.size()!! val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.constIndex()!!
asmgen.out(""" asmgen.out("""
lda #<$identifierName lda #<$identifierName
sta $ESTACK_LO_HEX,x sta P8ESTACK_LO,x
lda #>$identifierName lda #>$identifierName
sta $ESTACK_HI_HEX,x sta P8ESTACK_HI,x
dex dex
lda #$size lda #$size
sta $ESTACK_LO_HEX,x sta P8ESTACK_LO,x
dex
""")
}
private fun outputPushAddressOfIdentifier(arg: Expression) {
val identifierName = asmgen.asmIdentifierName(arg as IdentifierReference)
asmgen.out("""
lda #<$identifierName
sta $ESTACK_LO_HEX,x
lda #>$identifierName
sta $ESTACK_HI_HEX,x
dex dex
""") """)
} }

View File

@ -4,13 +4,11 @@ import prog8.ast.IFunctionCall
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX import prog8.compiler.target.c64.codegen.assignment.*
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.toHex
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -19,220 +17,219 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// output the code to setup the parameters and perform the actual call // output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values! // 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 sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val saveX = Register.X in sub.asmClobbers || sub.regXasResult() val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
if(saveX) if(saveX)
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y... asmgen.saveRegister(CpuRegister.X)
val subName = asmgen.asmIdentifierName(stmt.target) val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) { if(stmt.args.isNotEmpty()) {
for(arg in sub.parameters.withIndex().zip(stmt.args)) { if(sub.asmParameterRegisters.isEmpty()) {
translateFuncArguments(arg.first, arg.second, sub) // via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0])
} else {
// multiple register arguments, risk of register clobbering.
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
when {
stmt.args.all {it is AddressOf ||
it is NumericLiteralValue ||
it is StringLiteralValue ||
it is ArrayLiteralValue ||
it is IdentifierReference} -> {
// no risk of clobbering for these simple argument types. Optimize the register loading.
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaRegister(sub, arg.first, arg.second)
}
}
else -> {
// Risk of clobbering due to complex expression args. Work via the stack.
registerArgsViaStackEvaluation(stmt, sub)
}
}
}
} }
} }
asmgen.out(" jsr $subName") asmgen.out(" jsr $subName")
if(saveX) if(saveX)
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again asmgen.restoreRegister(CpuRegister.X)
} }
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) { private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
val sourceIDt = value.inferType(program) // this is called when one or more of the arguments are 'complex' and
if(!sourceIDt.isKnown) // cannot be assigned to a register easily or risk clobbering other registers.
if(sub.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
asmgen.out(" inx") // align estack pointer
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
when {
argi.value.second.stack -> TODO("asmsub @stack parameter")
argi.value.second.statusflag == Statusflag.Pc -> {
require(argForCarry == null)
argForCarry = argi
}
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
require(argForXregister==null)
argForXregister = argi
}
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
require(argForAregister == null)
argForAregister = argi
}
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
asmgen.out(" ldy P8ESTACK_LO+${argi.index},x")
}
else -> throw AssemblyError("weird argument")
}
}
if(argForCarry!=null) {
asmgen.out("""
lda P8ESTACK_LO+${argForCarry.index},x
beq +
sec
bcs ++
+ clc
+ php""") // push the status flags
}
if(argForAregister!=null) {
when(argForAregister.value.second.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x")
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x | ldy P8ESTACK_HI+${argForAregister.index},x")
else -> throw AssemblyError("weird arg")
}
}
if(argForXregister!=null) {
if(argForAregister!=null)
asmgen.out(" pha")
when(argForXregister.value.second.registerOrPair) {
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO+${argForXregister.index},x | tax")
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO+${argForXregister.index},x | lda P8ESTACK_HI+${argForXregister.index},x | tax | tya")
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI+${argForXregister.index},x | lda P8ESTACK_LO+${argForXregister.index},x | tax")
else -> throw AssemblyError("weird arg")
}
if(argForAregister!=null)
asmgen.out(" pla")
} else {
repeat(sub.parameters.size - 1) { asmgen.out(" inx") } // unwind stack
}
if(argForCarry!=null)
asmgen.out(" plp") // set the carry flag back to correct value
}
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown") throw AssemblyError("arg type unknown")
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT) val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(sourceDt, parameter.value.type)) if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible") throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) {
// pass parameter via a regular variable (not via registers) val scopedParamVar = (sub.scopedname+"."+parameter.value.name).split(".")
val paramVar = parameter.value val identifier = IdentifierReference(scopedParamVar, sub.position)
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".") identifier.linkParents(value.parent)
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position) val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, variable = identifier)
target.linkParents(value.parent) val source = AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(tgt)
when (value) { val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
is NumericLiteralValue -> { asmgen.translateNormalAssignment(asgn)
// optimize when the argument is a constant literal }
when(parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort()) private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt()) // pass argument via a register parameter
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble()) val valueIDt = value.inferType(program)
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh if(!valueIDt.isKnown)
else -> throw AssemblyError("weird parameter datatype") throw AssemblyError("arg type unknown")
} val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
} if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
is IdentifierReference -> { throw AssemblyError("argument type incompatible")
// optimize when the argument is a variable
when (parameter.value.type) { val paramRegister = sub.asmParameterRegisters[parameter.index]
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value) val statusflag = paramRegister.statusflag
in WordDatatypes -> asmgen.assignFromWordVariable(target, value) val register = paramRegister.registerOrPair
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value) val stack = paramRegister.stack
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh when {
else -> throw AssemblyError("weird parameter datatype") stack -> {
} // push arg onto the stack
} // note: argument order is reversed (first argument will be deepest on the stack)
is RegisterExpr -> { asmgen.translateExpression(value)
asmgen.assignFromRegister(target, value.register) }
} statusflag!=null -> {
is DirectMemoryRead -> { if (statusflag == Statusflag.Pc) {
when(value.addressExpression) { // this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> { is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt() val carrySet = value.number.toInt() != 0
asmgen.assignFromMemoryByte(target, address, null) asmgen.out(if(carrySet) " sec" else " clc")
} }
is IdentifierReference -> { is IdentifierReference -> {
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference) val sourceName = asmgen.asmVariableName(value)
} asmgen.out("""
else -> { pha
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, Register.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
}
}
} else {
// pass parameter via a register parameter
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
}
statusflag!=null -> {
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
asmgen.out("""
lda $sourceName lda $sourceName
beq + beq +
sec sec
bcs ++ bcs ++
+ clc + clc
+ + pla
""") """)
} }
is RegisterExpr -> { else -> {
when(value.register) { asmgen.translateExpression(value)
Register.A -> asmgen.out(" cmp #0") asmgen.out("""
Register.X -> asmgen.out(" txa") inx
Register.Y -> asmgen.out(" tya") pha
} lda P8ESTACK_LO,x
asmgen.out("""
beq +
sec
bcs ++
+ clc
+
""")
}
else -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
beq + beq +
sec sec
bcs ++ bcs ++
+ clc + clc
+ + pla
""") """)
}
}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
register!=null && register.name.length==1 -> {
when (value) {
is NumericLiteralValue -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteConstant(target, value.number.toShort())
}
is IdentifierReference -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteVariable(target, value)
}
else -> {
asmgen.translateExpression(value)
when(register) {
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
else -> throw AssemblyError("cannot assign to register pair")
}
} }
} }
} }
register!=null && register.name.length==2 -> { else throw AssemblyError("can only use Carry as status flag parameter")
// register pair as a 16-bit value (only possible for subroutine parameters) }
when (value) { else -> {
is NumericLiteralValue -> { // via register or register pair
// optimize when the argument is a constant literal val target = AsmAssignTarget.fromRegisters(register!!, program, asmgen)
val hex = value.number.toHex() val src = if(valueDt in PassByReferenceDatatypes) {
when (register) { val addr = AddressOf(value as IdentifierReference, Position.DUMMY)
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex") AsmAssignSource.fromAstSource(addr, program).adjustDataTypeToTarget(target)
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex") } else {
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex") AsmAssignSource.fromAstSource(value, program).adjustDataTypeToTarget(target)
else -> {}
}
}
is AddressOf -> {
// optimize when the argument is an address of something
val sourceName = asmgen.asmIdentifierName(value.identifier)
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
if(sourceDt in PassByReferenceDatatypes) {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
} else {
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
else -> {}
}
}
}
else -> {
asmgen.translateExpression(value)
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
throw AssemblyError("can't use X register here - use a variable")
else if (register == RegisterOrPair.AY)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
}
}
} }
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
} }
} }
} }
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean { private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType) if(argType isAssignableTo paramType)
return true return true
if(argType in ByteDatatypes && paramType in ByteDatatypes) if(argType in ByteDatatypes && paramType in ByteDatatypes)

View File

@ -4,10 +4,8 @@ import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RegisterExpr
import prog8.ast.statements.PostIncrDecr import prog8.ast.statements.PostIncrDecr
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.toHex import prog8.compiler.toHex
@ -17,28 +15,10 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
val targetIdent = stmt.target.identifier val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed val targetArrayIdx = stmt.target.arrayindexed
val targetRegister = stmt.target.register
when { when {
targetRegister!=null -> {
when(targetRegister) {
Register.A -> {
if(incr)
asmgen.out(" clc | adc #1 ")
else
asmgen.out(" sec | sbc #1 ")
}
Register.X -> {
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
}
Register.Y -> {
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
}
}
}
targetIdent!=null -> { targetIdent!=null -> {
val what = asmgen.asmIdentifierName(targetIdent) val what = asmgen.asmVariableName(targetIdent)
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT) when (stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)) {
when (dt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what") in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> { in WordDatatypes -> {
if(incr) if(incr)
@ -53,7 +33,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
} }
DataType.FLOAT -> { DataType.FLOAT -> {
asmgen.out(" lda #<$what | ldy #>$what") 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") else -> throw AssemblyError("need numeric type")
} }
@ -65,87 +45,91 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
asmgen.out(if(incr) " inc $what" else " dec $what") asmgen.out(if(incr) " inc $what" else " dec $what")
} }
is IdentifierReference -> { is IdentifierReference -> {
val what = asmgen.asmIdentifierName(addressExpr) val what = asmgen.asmVariableName(addressExpr)
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2") asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
if(incr) if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified") asmgen.out("+\tinc ${'$'}ffff\t; modified")
else else
asmgen.out("+\tdec ${'$'}ffff\t; modified") asmgen.out("+\tdec ${'$'}ffff\t; modified")
} }
else -> throw AssemblyError("weird target type $targetMemory") else -> {
asmgen.translateExpression(addressExpr)
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta (+) + 1
lda P8ESTACK_HI,x
sta (+) + 2
""")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
} }
} }
targetArrayIdx!=null -> { targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index val index = targetArrayIdx.arrayspec.index
val what = asmgen.asmIdentifierName(targetArrayIdx.identifier) val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.identifier)
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT) val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
val elementDt = ArrayElementTypes.getValue(arrayDt)
when(index) { when(index) {
is NumericLiteralValue -> { is NumericLiteralValue -> {
val indexValue = index.number.toInt() * elementDt.memorySize() val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) { when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue") in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> { in WordDatatypes -> {
if(incr) if(incr)
asmgen.out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+") asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
else else
asmgen.out(""" asmgen.out("""
lda $what+$indexValue lda $asmArrayvarname+$indexValue
bne + bne +
dec $what+$indexValue+1 dec $asmArrayvarname+$indexValue+1
+ dec $what+$indexValue + dec $asmArrayvarname+$indexValue
""") """)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
asmgen.out(" lda #<$what+$indexValue | ldy #>$what+$indexValue") asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f") asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
} }
else -> throw AssemblyError("need numeric type") else -> throw AssemblyError("need numeric type")
} }
} }
is RegisterExpr -> {
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
is IdentifierReference -> {
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
else -> { else -> {
asmgen.translateArrayIndexIntoA(targetArrayIdx) asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what) 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 floats.inc_var_f""")
}
else -> throw AssemblyError("weird array elt dt")
}
asmgen.restoreRegister(CpuRegister.X)
} }
} }
} }
else -> throw AssemblyError("weird target type ${stmt.target}") else -> throw AssemblyError("weird target type ${stmt.target}")
} }
} }
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
asmgen.out(" stx ${C64Zeropage.SCRATCH_REG_X} | tax")
when(arrayDt) {
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
if(incr)
asmgen.out(" inc $arrayVarName,x | bne + | inc $arrayVarName+1,x |+")
else
asmgen.out("""
lda $arrayVarName,x
bne +
dec $arrayVarName+1,x
+ dec $arrayVarName
""")
}
DataType.ARRAY_F -> {
asmgen.out(" lda #<$arrayVarName | ldy #>$arrayVarName")
asmgen.out(if(incr) " jsr c64flt.inc_indexed_var_f" else " jsr c64flt.dec_indexed_var_f")
}
else -> throw AssemblyError("weird array dt")
}
asmgen.out(" ldx ${C64Zeropage.SCRATCH_REG_X}")
}
} }

View File

@ -0,0 +1,165 @@
package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.AsmGen
internal enum class TargetStorageKind {
VARIABLE,
ARRAY,
MEMORY,
REGISTER,
STACK
}
internal enum class SourceStorageKind {
LITERALNUMBER,
VARIABLE,
ARRAY,
MEMORY,
REGISTER,
STACK, // value is already present on stack
EXPRESSION, // expression in ast-form, still to be evaluated
}
internal class AsmAssignTarget(val kind: TargetStorageKind,
program: Program,
asmgen: AsmGen,
val datatype: DataType,
val variable: IdentifierReference? = null,
val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryWrite? = null,
val register: RegisterOrPair? = null,
val origAstTarget: AssignTarget? = null
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
val vardecl by lazy { variable!!.targetVarDecl(program.namespace)!! }
val asmVarname by lazy {
if(variable!=null)
asmgen.asmVariableName(variable)
else
asmgen.asmVariableName(array!!.identifier)
}
lateinit var origAssign: AsmAssignment
init {
if(variable!=null && vardecl.type == VarDeclType.CONST)
throw AssemblyError("can't assign to a constant")
if(register!=null && datatype !in IntegerDatatypes)
throw AssemblyError("register must be integer type")
}
companion object {
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
val dt = inferType(program, assign).typeOrElse(DataType.STRUCT)
when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, variable=identifier, origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, array = arrayindexed, origAstTarget = this)
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, memory = memoryAddress, origAstTarget = this)
else -> throw AssemblyError("weird target")
}
}
fun fromRegisters(registers: RegisterOrPair, program: Program, asmgen: AsmGen): AsmAssignTarget =
when(registers) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, register = registers)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, register = registers)
}
}
}
internal class AsmAssignSource(val kind: SourceStorageKind,
private val program: Program,
val datatype: DataType,
val variable: IdentifierReference? = null,
val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryRead? = null,
val register: CpuRegister? = null,
val number: NumericLiteralValue? = null,
val expression: Expression? = null
)
{
val constMemoryAddress by lazy { memory?.addressExpression?.constValue(program)?.number?.toInt() ?: 0}
val constArrayIndexValue by lazy { array?.arrayspec?.constIndex() }
val vardecl by lazy { variable?.targetVarDecl(program.namespace)!! }
companion object {
fun fromAstSource(value: Expression, program: Program): AsmAssignSource {
val cv = value.constValue(program)
if(cv!=null)
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, cv.type, number = cv)
return when(value) {
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, value.type, number = cv)
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.VARIABLE, program, dt, variable = value)
}
is DirectMemoryRead -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, DataType.UBYTE, memory = value)
}
is ArrayIndexedExpression -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.ARRAY, program, dt, array = value)
}
else -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, dt, expression = value)
}
}
}
}
fun getAstValue(): Expression = when(kind) {
SourceStorageKind.LITERALNUMBER -> number!!
SourceStorageKind.VARIABLE -> variable!!
SourceStorageKind.ARRAY -> array!!
SourceStorageKind.MEMORY -> memory!!
SourceStorageKind.EXPRESSION -> expression!!
SourceStorageKind.REGISTER -> throw AssemblyError("cannot get a register source as Ast node")
SourceStorageKind.STACK -> throw AssemblyError("cannot get a stack source as Ast node")
}
fun withAdjustedDt(newType: DataType) =
AsmAssignSource(kind, program, newType, variable, array, memory, register, number, expression)
fun adjustDataTypeToTarget(target: AsmAssignTarget): AsmAssignSource {
// allow some signed/unsigned relaxations
if(target.datatype!=datatype) {
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
return withAdjustedDt(target.datatype)
} else if(target.datatype in WordDatatypes && datatype in WordDatatypes) {
return withAdjustedDt(target.datatype)
}
}
return this
}
}
internal class AsmAssignment(val source: AsmAssignSource,
val target: AsmAssignTarget,
val isAugmentable: Boolean,
val position: Position) {
init {
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" }
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,118 @@
package prog8.compiler.target.cx16
import prog8.ast.Program
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.parser.ModuleImporter
import java.io.IOException
internal object CX16MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU65c02
// 5-byte cbm MFLPT format limitations:
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MEM_SIZE = 5
override val POINTER_MEM_SIZE = 2
override val BASIC_LOAD_ADDRESS = 0x0801
override val RAW_LOAD_ADDRESS = 0x8000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above
override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
override fun getFloatRomConst(number: Double): String? = null // Cx16 has no pulblic ROM float locations
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib")
}
override fun launchEmulator(programName: String) {
for(emulator in listOf("x16emu")) {
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-rom", "/usr/share/x16-rom/rom.bin", "-scale", "2",
"-run", "-prg", programName + ".prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(x: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
}
override fun isRegularRAMaddress(address: Int): Boolean = address < 0x9f00 || address in 0xa000..0xbfff
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
}
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
override val opcodeNames = setOf("adc", "and", "asl", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "inx", "iny", "jmp", "jsr",
"lda", "ldx", "ldy", "lsr", "nop", "ora", "pha", "php",
"pla", "plp", "rol", "ror", "rti", "rts", "sbc",
"sec", "sed", "sei",
"sta", "stx", "sty", "tax", "tay", "tsx", "txa", "txs", "tya",
"bra", "phx", "phy", "plx", "ply", "stz", "trb", "tsb", "bbr", "bbs",
"rmb", "smb", "stp", "wai")
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x79 // temp storage for a single byte
override val SCRATCH_REG = 0x7a // temp storage for a register
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f
init {
if (options.floats && options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22..0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22..0x7f)
free.addAll(0xa9..0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22..0x7f)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
else -> throw CompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
}
require(SCRATCH_B1 !in free)
require(SCRATCH_REG !in free)
require(SCRATCH_W1 !in free)
require(SCRATCH_W2 !in free)
for (reserved in options.zpReserved)
reserve(reserved)
}
}
}

View File

@ -3,6 +3,8 @@ package prog8.functions
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.compiler.CompilerException import prog8.compiler.CompilerException
import kotlin.math.* import kotlin.math.*
@ -25,8 +27,6 @@ val BuiltinFunctions = mapOf(
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), "ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), "rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), "ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"lsl" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
"lsr" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null), "sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null), "reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// these few have a return value depending on the argument(s): // these few have a return value depending on the argument(s):
@ -35,6 +35,7 @@ val BuiltinFunctions = mapOf(
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args "sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument "abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length "len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
// normal functions follow: // normal functions follow:
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ), "sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) }, "sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
@ -62,7 +63,7 @@ val BuiltinFunctions = mapOf(
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) }, "all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }}, "lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}}, "msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FSignature(true, listOf(FParam("lsb", setOf(DataType.UBYTE)), FParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword), "mkword" to FSignature(true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
"rnd" to FSignature(true, emptyList(), DataType.UBYTE), "rnd" to FSignature(true, emptyList(), DataType.UBYTE),
"rndw" to FSignature(true, emptyList(), DataType.UWORD), "rndw" to FSignature(true, emptyList(), DataType.UWORD),
"rndf" to FSignature(true, emptyList(), DataType.FLOAT), "rndf" to FSignature(true, emptyList(), DataType.FLOAT),
@ -103,9 +104,9 @@ val BuiltinFunctions = mapOf(
FParam("length", setOf(DataType.UBYTE))), null) FParam("length", setOf(DataType.UBYTE))), null)
) )
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!! fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
fun builtinMin(array: List<Number>): Number = array.minBy { it.toDouble() }!! fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() } fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
@ -240,59 +241,88 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
} }
} }
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position)
if(args[0] !is IdentifierReference)
throw SyntaxError("sizeof argument should be an identifier", position)
val dt = args[0].inferType(program)
if(dt.isKnown) {
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
?: throw CannotEvaluateException("sizeof", "no target")
fun structSize(target: StructDecl) =
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position)
return when {
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
numericLiteral(elementDt.memorySize() * length, position)
}
dt.istype(DataType.STRUCT) -> {
when (target) {
is VarDecl -> structSize(target.struct!!)
is StructDecl -> structSize(target)
else -> throw CompilerException("weird struct type $target")
}
}
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
}
}
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("strlen requires one argument", position) throw SyntaxError("strlen requires one argument", position)
val argument = args[0].constValue(program) ?: throw NotConstArgumentException() val argument=args[0]
if(argument.type != DataType.STR) if(argument is StringLiteralValue)
throw SyntaxError("strlen must have string argument", position) return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
throw NotConstArgumentException() // this function is not considering the string argument a constant if(vardecl!=null) {
if(vardecl.datatype!=DataType.STR)
throw SyntaxError("strlen must have string argument", position)
if(vardecl.autogeneratedDontRemove) {
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
}
}
throw NotConstArgumentException()
} }
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE. // note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1) if(args.size!=1)
throw SyntaxError("len requires one argument", position) throw SyntaxError("len requires one argument", position)
val constArg = args[0].constValue(program)
if(constArg!=null)
throw SyntaxError("len of weird argument ${args[0]}", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace) val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
var arraySize = directMemVar?.arraysize?.size() var arraySize = directMemVar?.arraysize?.constIndex()
if(arraySize != null) if(arraySize != null)
return NumericLiteralValue.optimalInteger(arraySize, position) return NumericLiteralValue.optimalInteger(arraySize, position)
if(args[0] is ArrayLiteralValue) if(args[0] is ArrayLiteralValue)
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position) return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
if(args[0] !is IdentifierReference) if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position) throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace) val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
?: throw CannotEvaluateException("len", "no target vardecl") ?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) { return when(target.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F -> {
arraySize = target.arraysize?.size() arraySize = target.arraysize?.constIndex()
if(arraySize==null) if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown") throw CannotEvaluateException("len", "arraysize unknown")
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.ARRAY_F -> {
arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position) NumericLiteralValue.optimalInteger(arraySize, args[0].position)
} }
DataType.STR -> { DataType.STR -> {
val refLv = target.value as StringLiteralValue val refLv = target.value as StringLiteralValue
if(refLv.value.length>255)
throw CompilerException("string length exceeds byte limit ${refLv.position}")
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position) NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
} }
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position) DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
else -> throw CompilerException("weird datatype") else -> throw CompilerException("weird datatype")
} }
} }
@ -300,9 +330,9 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 2) if (args.size != 2)
throw SyntaxError("mkword requires lsb and msb arguments", position) throw SyntaxError("mkword requires msb and lsb arguments", position)
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException() val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException() val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt() val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
return NumericLiteralValue(DataType.UWORD, result, position) return NumericLiteralValue(DataType.UWORD, result, position)
} }
@ -312,7 +342,7 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
throw SyntaxError("sin8 requires one argument", position) throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toShort(), position) return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
} }
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@ -320,7 +350,7 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
throw SyntaxError("sin8u requires one argument", position) throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort(), position) return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
} }
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@ -328,7 +358,7 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
throw SyntaxError("cos8 requires one argument", position) throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toShort(), position) return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
} }
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@ -336,7 +366,7 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
throw SyntaxError("cos8u requires one argument", position) throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort(), position) return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
} }
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@ -375,7 +405,7 @@ private fun builtinSgn(args: List<Expression>, position: Position, program: Prog
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sgn requires one argument", position) throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toShort(), position) return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toInt().toShort(), position)
} }
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue { private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
@ -387,8 +417,8 @@ private fun numericLiteral(value: Number, position: Position): NumericLiteralVal
floatNum floatNum
return when(tweakedValue) { return when(tweakedValue) {
is Int -> NumericLiteralValue.optimalNumeric(value.toInt(), position) is Int -> NumericLiteralValue.optimalInteger(value.toInt(), position)
is Short -> NumericLiteralValue.optimalNumeric(value.toInt(), position) is Short -> NumericLiteralValue.optimalInteger(value.toInt(), position)
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position) is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position) is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position) is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)

View File

@ -1,157 +0,0 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.expressions.BinaryExpression
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Assignment
import prog8.ast.statements.PostIncrDecr
internal class AssignmentTransformer(val program: Program, val errors: ErrorReporter) : AstWalker() {
var optimizationsDone: Int = 0
private val noModifications = emptyList<IAstModification>()
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// modify A = A + 5 back into augmented form A += 5 for easier code generation for optimized in-place assignments
// also to put code generation stuff together, single value assignment (A = 5) is converted to a special
// augmented form as wel (with the operator "setvalue")
if (assignment.aug_op == null) {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
if (assignment.target.isSameAs(binExpr.left)) {
assignment.value = binExpr.right
assignment.aug_op = binExpr.operator + "="
assignment.value.parent = assignment
optimizationsDone++
return noModifications
}
}
assignment.aug_op = "setvalue"
optimizationsDone++
} else if(assignment.aug_op == "+=") {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
if(binExpr.operator == "+") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x += y + 1 -> x += y , x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x += y + 2 -> x += y , x++, x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
}
} else if(binExpr.operator == "-") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x += y - 1 -> x += y , x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x += y - 2 -> x += y , x--, x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
}
}
}
} else if(assignment.aug_op == "-=") {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
if(binExpr.operator == "+") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x -= y + 1 -> x -= y , x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x -= y + 2 -> x -= y , x--, x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
}
} else if(binExpr.operator == "-") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x -= y - 1 -> x -= y , x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x -= y - 2 -> x -= y , x++, x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
}
}
}
}
return noModifications
}
}

View File

@ -14,8 +14,7 @@ import prog8.compiler.loadAsmIncludeFile
private val alwaysKeepSubroutines = setOf( private val alwaysKeepSubroutines = setOf(
Pair("main", "start"), Pair("main", "start"),
Pair("irq", "irq"), Pair("irq", "irq")
Pair("prog8_lib", "init_system")
) )
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)

View File

@ -132,11 +132,11 @@ class ConstExprEvaluator {
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) { if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) { if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position) return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toShort(), left.position)
} }
} else if(left.type== DataType.UWORD) { } else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) { if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position) return NumericLiteralValue(DataType.UWORD, left.number.toInt() and right.number.toInt(), left.position)
} }
} }
throw ExpressionError("cannot calculate $left & $right", left.position) throw ExpressionError("cannot calculate $left & $right", left.position)
@ -163,7 +163,7 @@ class ConstExprEvaluator {
val error = "cannot add $left and $right" val error = "cannot add $left and $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
@ -180,7 +180,7 @@ class ConstExprEvaluator {
val error = "cannot subtract $left and $right" val error = "cannot subtract $left and $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
@ -197,7 +197,7 @@ class ConstExprEvaluator {
val error = "cannot multiply ${left.type} and ${right.type}" val error = "cannot multiply ${left.type} and ${right.type}"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
@ -220,7 +220,7 @@ class ConstExprEvaluator {
in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt() val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteralValue.optimalNumeric(result, left.position) NumericLiteralValue.optimalInteger(result, left.position)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position) if(right.number.toDouble()==0.0) divideByZeroError(right.position)

View File

@ -7,179 +7,9 @@ import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification import prog8.ast.processing.IAstModification
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
// First thing to do is replace all constant identifiers with their actual value, internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
// 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?.size()==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?.size()
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?.size() ?: 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) as Expression}.toTypedArray()
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?.size() ?: 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?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(rangeExpr==null && litval!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", litval.position)
else {
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
val 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
return listOf(IAstModification.ReplaceNode(decl.value!!, declValue.cast(decl.datatype), decl))
}
return noModifications
}
}
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> { override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
@ -203,7 +33,7 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
"-" -> when (subexpr.type) { "-" -> when (subexpr.type) {
in IntegerDatatypes -> { in IntegerDatatypes -> {
listOf(IAstModification.ReplaceNode(expr, listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position), NumericLiteralValue.optimalInteger(-subexpr.number.toInt(), subexpr.position),
parent)) parent))
} }
DataType.FLOAT -> { DataType.FLOAT -> {
@ -214,9 +44,24 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
else -> throw ExpressionError("can only take negative of int or float", subexpr.position) else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
} }
"~" -> when (subexpr.type) { "~" -> when (subexpr.type) {
in IntegerDatatypes -> { DataType.BYTE -> {
listOf(IAstModification.ReplaceNode(expr, listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalNumeric(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)) parent))
} }
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position) else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
@ -273,11 +118,8 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
// const fold when both operands are a const // const fold when both operands are a const
if(leftconst != null && rightconst != null) { if(leftconst != null && rightconst != null) {
val evaluator = ConstExprEvaluator() val evaluator = ConstExprEvaluator()
return listOf(IAstModification.ReplaceNode( val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
expr, return listOf(IAstModification.ReplaceNode(expr, result, parent))
evaluator.evaluate(leftconst, expr.operator, rightconst),
parent
))
} }
return noModifications return noModifications
@ -319,21 +161,24 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
} }
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> { override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr { fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr? {
val newFrom: NumericLiteralValue val fromCast = rangeFrom.cast(targetDt)
val newTo: NumericLiteralValue val toCast = rangeTo.cast(targetDt)
try { if(!fromCast.isValid || !toCast.isValid)
newFrom = rangeFrom.cast(targetDt) return null
newTo = rangeTo.cast(targetDt)
} catch (x: ExpressionError) { val newStep =
return range if(stepLiteral!=null) {
} val stepCast = stepLiteral.cast(targetDt)
val newStep: Expression = try { if(stepCast.isValid)
stepLiteral?.cast(targetDt)?: range.step stepCast.valueOrZero()
} catch(ee: ExpressionError) { else
range.step range.step
} } else {
return RangeExpr(newFrom, newTo, newStep, range.position) range.step
}
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
} }
// adjust the datatype of a range expression in for loops to the loop variable. // adjust the datatype of a range expression in for loops to the loop variable.
@ -342,45 +187,60 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
val rangeTo = iterableRange.to as? NumericLiteralValue val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return noModifications if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar?.targetVarDecl(program.namespace) val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!!
if(loopvar!=null) { val stepLiteral = iterableRange.step as? NumericLiteralValue
val stepLiteral = iterableRange.step as? NumericLiteralValue when(loopvar.datatype) {
when(loopvar.datatype) { DataType.UBYTE -> {
DataType.UBYTE -> { if(rangeFrom.type!= DataType.UBYTE) {
if(rangeFrom.type!= DataType.UBYTE) { // attempt to translate the iterable into ubyte values
// attempt to translate the iterable into ubyte values val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop)) return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
} }
DataType.BYTE -> {
if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.UWORD -> {
if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.WORD -> {
if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
} }
DataType.BYTE -> {
if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.UWORD -> {
if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.WORD -> {
if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
} }
return noModifications return noModifications
} }
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val numval = decl.value as? NumericLiteralValue
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(!valueDt.istype(decl.datatype)) {
val cast = numval.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
}
return noModifications
}
private class ShuffleOperands(val expr: BinaryExpression, private class ShuffleOperands(val expr: BinaryExpression,
val exprOperator: String?, val exprOperator: String?,
val subExpr: BinaryExpression, val subExpr: BinaryExpression,
@ -408,8 +268,9 @@ internal class ConstantFoldingOptimizer(private val program: Program, private va
// todo: this implements only a small set of possible reorderings at this time // todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) { if(expr.operator==subExpr.operator) {
// both operators are the same. // both operators are the same.
// If + or *, we can simply shuffle the const operands around to optimize.
if(expr.operator=="+" || expr.operator=="*") { // If associative, we can simply shuffle the const operands around to optimize.
if(expr.operator in associativeOperators) {
return if(leftIsConst) { return if(leftIsConst) {
if(subleftIsConst) if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left) ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)

View File

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

View File

@ -1,12 +1,15 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification import prog8.ast.processing.IAstModification
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.statements.VarDecl
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.pow import kotlin.math.pow
@ -24,12 +27,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet() private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if (assignment.aug_op != null)
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
return noModifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>() val mods = mutableListOf<IAstModification>()
@ -37,19 +34,22 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
val literal = typecast.expression as? NumericLiteralValue val literal = typecast.expression as? NumericLiteralValue
if (literal != null) { if (literal != null) {
val newLiteral = literal.cast(typecast.type) val newLiteral = literal.cast(typecast.type)
if (newLiteral !== literal) if (newLiteral.isValid && newLiteral.valueOrZero() !== literal)
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral, typecast) mods += IAstModification.ReplaceNode(typecast.expression, newLiteral.valueOrZero(), typecast)
} }
// remove redundant nested typecasts: // remove redundant nested typecasts
// if the typecast casts a value to the same type, remove the cast.
// if the typecast contains another typecast, remove the inner typecast.
val subTypecast = typecast.expression as? TypecastExpression val subTypecast = typecast.expression as? TypecastExpression
if (subTypecast != null) { if (subTypecast != null) {
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast) // remove the sub-typecast if its datatype is larger than the outer typecast
if(subTypecast.type largerThan typecast.type) {
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
}
} else { } else {
if (typecast.expression.inferType(program).istype(typecast.type)) if (typecast.expression.inferType(program).istype(typecast.type)) {
// remove duplicate cast
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent) mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
}
} }
return mods return mods
@ -179,28 +179,6 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
// unsigned >= 0 --> true // unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent)) return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
} }
when(leftDt) {
DataType.BYTE -> {
// signed >=0 --> signed ^ $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
DataType.WORD -> {
// signedw >=0 --> msb(signedw) ^ $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
mutableListOf(expr.left),
expr.position
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
else -> {}
}
} }
if(expr.operator == "<" && rightVal?.number == 0) { if(expr.operator == "<" && rightVal?.number == 0) {
@ -301,6 +279,123 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
return noModifications return noModifications
} }
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
// if(decl.type==VarDeclType.VAR ) {
// val binExpr = decl.value as? BinaryExpression
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
// val assign = Assignment(target, augExpr, binExpr.position)
// println("SPLIT VARDECL $decl")
// return listOf(
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
// IAstModification.InsertAfter(decl, assign, parent)
// )
// }
// }
// return noModifications
// }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
/*
reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps:
X = BinExpr X = LeftExpr
<operator> followed by
/ \ IF 'X' not used X = BinExpr
/ \ IN LEFTEXPR ==> <operator>
/ \ / \
LeftExpr. RightExpr. / \
/ \ / \ X RightExpr.
.. .. .. ..
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
if (!assignment.isAugmentable) {
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf(
IAstModification.InsertBefore(assignment, firstAssign, parent),
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
}
}
// TODO further unraveling of binary expression trees into flat statements.
// however this should probably be done in a more generic way to also service
// the expressiontrees that are not used in an assignment statement...
}
return noModifications
}
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope): Boolean {
return when {
target.identifier!=null -> target.isInRegularRAM(namespace)
target.memoryAddress!=null -> target.isInRegularRAM(namespace)
target.arrayindexed!=null -> {
val index = target.arrayindexed!!.arrayspec.index
if(index is NumericLiteralValue)
target.isInRegularRAM(namespace)
else
false
}
else -> false
}
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
if(functionCall.target.nameInSource == listOf("lsb")) {
val arg = functionCall.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
// useless lsb() of byte value that was casted to word
return listOf(IAstModification.ReplaceNode(functionCall, arg.expression, parent))
}
} else {
val argDt = arg.inferType(program)
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
// useless lsb() of byte value
return listOf(IAstModification.ReplaceNode(functionCall, arg, parent))
}
}
}
else if(functionCall.target.nameInSource == listOf("msb")) {
val arg = functionCall.args[0]
if(arg is TypecastExpression) {
val valueDt = arg.expression.inferType(program)
if (valueDt.istype(DataType.BYTE) || valueDt.istype(DataType.UBYTE)) {
// useless msb() of byte value that was casted to word, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
NumericLiteralValue(valueDt.typeOrElse(DataType.UBYTE), 0, arg.expression.position),
parent))
}
} else {
val argDt = arg.inferType(program)
if (argDt.istype(DataType.BYTE) || argDt.istype(DataType.UBYTE)) {
// useless msb() of byte value, replace with 0
return listOf(IAstModification.ReplaceNode(
functionCall,
NumericLiteralValue(argDt.typeOrElse(DataType.UBYTE), 0, arg.position),
parent))
}
}
}
return noModifications
}
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? { private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when { return when {
subBinExpr.left isSameAs x -> subBinExpr.right subBinExpr.left isSameAs x -> subBinExpr.right
@ -590,10 +685,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
} else if (amount >= 8) { } else if (amount >= 8) {
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position) val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
if (amount == 8) { if (amount == 8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position) return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
} }
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position) val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position) return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
} }
} }
else -> { else -> {
@ -610,8 +705,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (amount == 0) { if (amount == 0) {
return expr.left return expr.left
} }
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT) when (expr.left.inferType(program).typeOrElse(DataType.STRUCT)) {
when (targetDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
if (amount >= 8) { if (amount >= 8) {
return NumericLiteralValue.optimalInteger(0, expr.position) return NumericLiteralValue.optimalInteger(0, expr.position)

View File

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

View File

@ -14,11 +14,6 @@ import prog8.functions.BuiltinFunctions
import kotlin.math.floor import kotlin.math.floor
/*
TODO: remove unreachable code after return and exit()
*/
internal class StatementOptimizer(private val program: Program, internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter) : AstWalker() { private val errors: ErrorReporter) : AstWalker() {
@ -46,15 +41,14 @@ internal class StatementOptimizer(private val program: Program,
if(subroutine.asmAddress==null && !forceOutput) { if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.containsNoCodeNorVars()) { if(subroutine.containsNoCodeNorVars()) {
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position) errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, parent)) val removals = callgraph.calledBy.getValue(subroutine).map {
IAstModification.Remove(it, it.parent)
}.toMutableList()
removals += IAstModification.Remove(subroutine, parent)
return removals
} }
} }
val linesToRemove = deduplicateAssignments(subroutine.statements)
if(linesToRemove.isNotEmpty()) {
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) { if(subroutine !in callgraph.usedSymbols && !forceOutput) {
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position) errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, parent)) return listOf(IAstModification.Remove(subroutine, parent))
@ -63,11 +57,6 @@ internal class StatementOptimizer(private val program: Program,
return noModifications 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> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in decl.definingBlock().options() val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) { if(decl !in callgraph.usedSymbols && !forceOutput) {
@ -101,32 +90,34 @@ internal class StatementOptimizer(private val program: Program,
} }
if(stringVar!=null) { if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program.namespace)!! val vardecl = stringVar.targetVarDecl(program.namespace)!!
val string = vardecl.value!! as StringLiteralValue val string = vardecl.value as? StringLiteralValue
val pos = functionCallStatement.position if(string!=null) {
if(string.value.length==1) { val pos = functionCallStatement.position
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0] if (string.value.length == 1) {
val chrout = FunctionCallStatement( val firstCharEncoded = CompilationTarget.instance.encodeString(string.value, string.altEncoding)[0]
IdentifierReference(listOf("c64", "CHROUT"), pos), val chrout = FunctionCallStatement(
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)), IdentifierReference(listOf("c64", "CHROUT"), pos),
functionCallStatement.void, pos mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
) functionCallStatement.void, pos
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent)) )
} else if(string.value.length==2) { return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding) } else if (string.value.length == 2) {
val chrout1 = FunctionCallStatement( val firstTwoCharsEncoded = CompilationTarget.instance.encodeString(string.value.take(2), string.altEncoding)
IdentifierReference(listOf("c64", "CHROUT"), pos), val chrout1 = FunctionCallStatement(
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)), IdentifierReference(listOf("c64", "CHROUT"), pos),
functionCallStatement.void, pos mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
) functionCallStatement.void, pos
val chrout2 = FunctionCallStatement( )
IdentifierReference(listOf("c64", "CHROUT"), pos), val chrout2 = FunctionCallStatement(
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)), IdentifierReference(listOf("c64", "CHROUT"), pos),
functionCallStatement.void, pos mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
) functionCallStatement.void, pos
val anonscope = AnonymousScope(mutableListOf(), pos) )
anonscope.statements.add(chrout1) val anonscope = AnonymousScope(mutableListOf(), pos)
anonscope.statements.add(chrout2) anonscope.statements.add(chrout1)
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent)) anonscope.statements.add(chrout2)
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
}
} }
} }
} }
@ -135,7 +126,7 @@ internal class StatementOptimizer(private val program: Program,
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace) val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
if(subroutine!=null) { if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is ReturnFromIrq || first is Return) if(first is Return)
return listOf(IAstModification.Remove(functionCallStatement, parent)) return listOf(IAstModification.Remove(functionCallStatement, parent))
} }
@ -191,11 +182,11 @@ internal class StatementOptimizer(private val program: Program,
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> { override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars()) { if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop errors.warn("removing empty for loop", forLoop.position)
return listOf(IAstModification.Remove(forLoop, parent)) return listOf(IAstModification.Remove(forLoop, parent))
} else if(forLoop.body.statements.size==1) { } else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) { if(loopvar!=null && loopvar.name==forLoop.loopVar.nameInSource.singleOrNull()) {
// remove empty for loop (only loopvar decl in it) // remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, parent)) return listOf(IAstModification.Remove(forLoop, parent))
} }
@ -207,7 +198,7 @@ internal class StatementOptimizer(private val program: Program,
// for loop over a (constant) range of just a single value-- optimize the loop away // for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block // loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position) val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position)) scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, forLoop.position))
scope.statements.addAll(forLoop.body.statements) scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
} }
@ -219,23 +210,23 @@ internal class StatementOptimizer(private val program: Program,
val size = sv.value.length val size = sv.value.length
if(size==1) { if(size==1) {
// loop over string of length 1 -> just assign the single character // loop over string of length 1 -> just assign the single character
val character = CompilationTarget.encodeString(sv.value, sv.altEncoding)[0] val character = CompilationTarget.instance.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position) val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position) val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, byte, forLoop.position)) scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
scope.statements.addAll(forLoop.body.statements) scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
} }
} }
else if(iterable.datatype in ArrayDatatypes) { else if(iterable.datatype in ArrayDatatypes) {
val size = iterable.arraysize!!.size() val size = iterable.arraysize!!.constIndex()
if(size==1) { if(size==1) {
// loop over array of length 1 -> just assign the single value // loop over array of length 1 -> just assign the single value
val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number
if(av!=null) { if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position) val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment( scope.statements.add(Assignment(
AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, NumericLiteralValue.optimalInteger(av.toInt(), iterable.position), AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
forLoop.position)) forLoop.position))
scope.statements.addAll(forLoop.body.statements) scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
@ -247,18 +238,18 @@ internal class StatementOptimizer(private val program: Program,
return noModifications return noModifications
} }
override fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> { override fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> {
val constvalue = repeatLoop.untilCondition.constValue(program) val constvalue = untilLoop.condition.constValue(program)
if(constvalue!=null) { if(constvalue!=null) {
if(constvalue.asBooleanValue) { if(constvalue.asBooleanValue) {
// always true -> keep only the statement block (if there are no continue and break statements) // always true -> keep only the statement block (if there are no break statements)
errors.warn("condition is always true", repeatLoop.untilCondition.position) errors.warn("condition is always true", untilLoop.condition.position)
if(!hasContinueOrBreak(repeatLoop.body)) if(!hasBreak(untilLoop.body))
return listOf(IAstModification.ReplaceNode(repeatLoop, repeatLoop.body, parent)) return listOf(IAstModification.ReplaceNode(untilLoop, untilLoop.body, parent))
} else { } else {
// always false // always false
val forever = ForeverLoop(repeatLoop.body, repeatLoop.position) val forever = RepeatLoop(null, untilLoop.body, untilLoop.position)
return listOf(IAstModification.ReplaceNode(repeatLoop, forever, parent)) return listOf(IAstModification.ReplaceNode(untilLoop, forever, parent))
} }
} }
return noModifications return noModifications
@ -269,7 +260,7 @@ internal class StatementOptimizer(private val program: Program,
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue) { return if(constvalue.asBooleanValue) {
// always true // always true
val forever = ForeverLoop(whileLoop.body, whileLoop.position) val forever = RepeatLoop(null, whileLoop.body, whileLoop.position)
listOf(IAstModification.ReplaceNode(whileLoop, forever, parent)) listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
} else { } else {
// always false -> remove the while statement altogether // always false -> remove the while statement altogether
@ -280,6 +271,26 @@ internal class StatementOptimizer(private val program: Program,
return noModifications return noModifications
} }
override fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val iter = repeatLoop.iterations
if(iter!=null) {
if(repeatLoop.body.containsNoCodeNorVars()) {
errors.warn("empty loop removed", repeatLoop.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
}
val iterations = iter.constValue(program)?.number?.toInt()
if (iterations == 0) {
errors.warn("iterations is always 0, removed loop", iter.position)
return listOf(IAstModification.Remove(repeatLoop, parent))
}
if (iterations == 1) {
errors.warn("iterations is always 1", iter.position)
return listOf(IAstModification.ReplaceNode(repeatLoop, repeatLoop.body, parent))
}
}
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> { override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
// remove empty choices // remove empty choices
class ChoiceRemover(val choice: WhenChoice) : IAstModification { class ChoiceRemover(val choice: WhenChoice) : IAstModification {
@ -302,40 +313,93 @@ internal class StatementOptimizer(private val program: Program,
return noModifications return noModifications
} }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.aug_op!=null)
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
// remove assignments to self val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null) {
if(binExpr.left isSameAs assignment.target) {
val rExpr = binExpr.right as? BinaryExpression
if(rExpr!=null) {
val op1 = binExpr.operator
val op2 = rExpr.operator
if(rExpr.left is NumericLiteralValue && op2 in setOf("+", "*", "&", "|")) {
// associative operator, make sure the constant numeric value is second (right)
return listOf(IAstModification.SwapOperands(rExpr))
}
val rNum = (rExpr.right as? NumericLiteralValue)?.number
if(rNum!=null) {
if (op1 == "+" || op1 == "-") {
if (op2 == "+") {
// A = A +/- B + N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
val addConstant = Assignment(
assignment.target,
BinaryExpression(binExpr.left, "+", rExpr.right, rExpr.position),
assignment.position
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, addConstant, parent))
} else if (op2 == "-") {
// A = A +/- B - N
val expr2 = BinaryExpression(binExpr.left, binExpr.operator, rExpr.left, binExpr.position)
val subConstant = Assignment(
assignment.target,
BinaryExpression(binExpr.left, "-", rExpr.right, rExpr.position),
assignment.position
)
return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
IAstModification.InsertAfter(assignment, subConstant, parent))
}
}
}
}
}
if(binExpr.operator in associativeOperators && binExpr.right isSameAs assignment.target) {
// associative operator, swap the operands so that the assignment target is first (left)
// unless the other operand is the same in which case we don't swap (endless loop!)
if (!(binExpr.left isSameAs binExpr.right))
return listOf(IAstModification.SwapOperands(binExpr))
}
}
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.target isSameAs assignment.value) { if(assignment.target isSameAs assignment.value) {
if(assignment.target.isNotMemory(program.namespace)) // remove assignment to self
return listOf(IAstModification.Remove(assignment, parent)) return listOf(IAstModification.Remove(assignment, parent))
} }
val targetIDt = assignment.target.inferType(program, assignment) val targetIDt = assignment.target.inferType(program, assignment)
if(!targetIDt.isKnown) if(!targetIDt.isKnown)
throw FatalAstException("can't infer type of assignment target") throw FatalAstException("can't infer type of assignment target")
// optimize binary expressions a bit // optimize binary expressions a bit
val targetDt = targetIDt.typeOrElse(DataType.STRUCT) val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) { if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.number?.toDouble() val rightCv = bexpr.right.constValue(program)?.number?.toDouble()
if (cv != null && assignment.target isSameAs bexpr.left) { if (rightCv != null && assignment.target isSameAs bexpr.left) {
// assignments of the form: X = X <operator> <expr> // assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0) // remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions // optimize/rewrite some other expressions
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) { when (bexpr.operator) {
"+" -> { "+" -> {
if (cv == 0.0) { if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent)) return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) { } else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) { if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several INCs (a bit less when dealing with memory targets) // 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) val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) { repeat(rightCv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position)) incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
} }
return listOf(IAstModification.ReplaceNode(assignment, incs, parent)) return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
@ -343,83 +407,41 @@ internal class StatementOptimizer(private val program: Program,
} }
} }
"-" -> { "-" -> {
if (cv == 0.0) { if (rightCv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent)) return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) { } else if (targetDt in IntegerDatatypes && floor(rightCv) == rightCv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) { if (vardeclDt != VarDeclType.MEMORY && rightCv in 1.0..4.0) {
// replace by several DECs (a bit less when dealing with memory targets) // 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) val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) { repeat(rightCv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position)) decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
} }
return listOf(IAstModification.ReplaceNode(assignment, decs, parent)) return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
} }
} }
} }
"*" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent)) "*" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"/" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent)) "/" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"**" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent)) "**" -> if (rightCv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"|" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent)) "|" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"^" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent)) "^" -> if (rightCv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"<<" -> { "<<" -> {
if (cv == 0.0) if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, parent)) return listOf(IAstModification.Remove(assignment, parent))
// replace by in-place lsl(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
mutableListOf(bexpr.left), true, assignment.position))
numshifts--
}
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
} }
">>" -> { ">>" -> {
if (cv == 0.0) if (rightCv == 0.0)
return listOf(IAstModification.Remove(assignment, parent)) return listOf(IAstModification.Remove(assignment, parent))
// replace by in-place lsr(...) call
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
mutableListOf(bexpr.left), true, assignment.position))
numshifts--
}
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
} }
} }
} }
} }
return noModifications return noModifications
} }
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> { private fun hasBreak(scope: INameScope): Boolean {
// 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
}
private fun hasContinueOrBreak(scope: INameScope): Boolean {
class Searcher: IAstVisitor class Searcher: IAstVisitor
{ {
@ -428,10 +450,6 @@ internal class StatementOptimizer(private val program: Program,
override fun visit(breakStmt: Break) { override fun visit(breakStmt: Break) {
count++ count++
} }
override fun visit(contStmt: Continue) {
count++
}
} }
val s=Searcher() val s=Searcher()

View File

@ -1,13 +1,15 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.processing.AstWalker import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification import prog8.ast.processing.IAstModification
import prog8.ast.statements.Block import prog8.ast.statements.*
internal class UnusedCodeRemover: AstWalker() { internal class UnusedCodeRemover(private val program: Program, private val errors: ErrorReporter): AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> { override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program) val callgraph = CallGraph(program)
@ -17,8 +19,9 @@ internal class UnusedCodeRemover: AstWalker() {
val entrypoint = program.entrypoint() val entrypoint = program.entrypoint()
program.modules.forEach { program.modules.forEach {
callgraph.forAllSubroutines(it) { sub -> callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (callgraph.calledBy[sub].isNullOrEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine))) 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() as Node))
}
} }
} }
@ -35,4 +38,64 @@ internal class UnusedCodeRemover: AstWalker() {
return removals return removals
} }
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
reportUnreachable(breakStmt, parent as INameScope)
return emptyList()
}
override fun before(jump: Jump, parent: Node): Iterable<IAstModification> {
reportUnreachable(jump, parent as INameScope)
return emptyList()
}
override fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> {
reportUnreachable(returnStmt, parent as INameScope)
return emptyList()
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.last() == "exit")
reportUnreachable(functionCallStatement, parent as INameScope)
return emptyList()
}
private fun reportUnreachable(stmt: Statement, parent: INameScope) {
when(val next = parent.nextSibling(stmt)) {
null, is Label, is Directive, is VarDecl, is InlineAssembly, is Subroutine, is StructDecl -> {}
else -> errors.warn("unreachable code", next.position)
}
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val removeDoubleAssignments = deduplicateAssignments(scope.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, scope) }
}
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
val removeDoubleAssignments = deduplicateAssignments(block.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, block) }
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val removeDoubleAssignments = deduplicateAssignments(subroutine.statements)
return removeDoubleAssignments.map { IAstModification.Remove(it, subroutine) }
}
private fun deduplicateAssignments(statements: List<Statement>): List<Assignment> {
// removes 'duplicate' assignments that assign the same target directly after another
val linesToRemove = mutableListOf<Assignment>()
for (stmtPairs in statements.windowed(2, step = 1)) {
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace))
linesToRemove.add(assign1)
}
}
return linesToRemove
}
} }

View File

@ -4,12 +4,12 @@ import org.antlr.v4.runtime.*
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.antlr.toAst import prog8.ast.antlr.toAst
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.base.SyntaxError import prog8.ast.base.SyntaxError
import prog8.ast.base.checkImportedValid import prog8.ast.base.checkImportedValid
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg import prog8.ast.statements.DirectiveArg
import prog8.compiler.target.CompilationTarget
import prog8.pathFrom import prog8.pathFrom
import java.io.InputStream import java.io.InputStream
import java.nio.file.Files import java.nio.file.Files
@ -20,21 +20,13 @@ import java.nio.file.Paths
internal class ParsingFailedError(override var message: String) : Exception(message) 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) internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.') internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
internal class ModuleImporter(private val errors: ErrorReporter) { internal class ModuleImporter {
internal fun importModule(program: Program, filePath: Path): Module { internal fun importModule(program: Program, filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'") print("importing '${moduleName(filePath.fileName)}'")
@ -61,13 +53,28 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
return executeImportDirective(program, import, Paths.get("")) 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 { private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = moduleName(modulePath.fileName) val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream) val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener() lexer.removeErrorListeners()
val lexerErrors = MyErrorListener()
lexer.addErrorListener(lexerErrors) lexer.addErrorListener(lexerErrors)
val tokens = CommentHandlingTokenStream(lexer) val tokens = CommentHandlingTokenStream(lexer)
val parser = prog8Parser(tokens) val parser = prog8Parser(tokens)
parser.removeErrorListeners()
parser.addErrorListener(MyErrorListener())
val parseTree = parser.module() val parseTree = parser.module()
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
if(numberOfErrors > 0) if(numberOfErrors > 0)
@ -95,7 +102,7 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path { private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8" val fileName = "$name.p8"
val locations = mutableListOf(source.parent) val locations = if(source.toString().isEmpty()) mutableListOf<Path>() else mutableListOf(source.parent)
val propPath = System.getProperty("prog8.libdir") val propPath = System.getProperty("prog8.libdir")
if(propPath!=null) if(propPath!=null)
@ -110,7 +117,7 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
if (Files.isReadable(file)) return file if (Files.isReadable(file)) return file
} }
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)") throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: embedded libs and $locations)")
} }
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? { private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
@ -124,16 +131,14 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
if(existing!=null) if(existing!=null)
return null return null
val resource = tryGetEmbeddedResource("$moduleName.p8") val rsc = tryGetEmbeddedResource("$moduleName.p8")
val importedModule = val importedModule =
if(resource!=null) { if(rsc!=null) {
// load the module from the embedded resource // load the module from the embedded resource
val (resource, resourcePath) = rsc
resource.use { resource.use {
if(import.args[0].int==42) println("importing '$moduleName' (library)")
println("importing '$moduleName' (library, auto)") importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"), true)
else
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
} }
} else { } else {
val modulePath = discoverImportedModuleFile(moduleName, source, import.position) val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
@ -144,7 +149,18 @@ internal class ModuleImporter(private val errors: ErrorReporter) {
return importedModule return importedModule
} }
private fun tryGetEmbeddedResource(name: String): InputStream? { private fun tryGetEmbeddedResource(name: String): Pair<InputStream, String>? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name") 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.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType import prog8.ast.Module
import prog8.ast.base.ErrorReporter import prog8.ast.Program
import prog8.ast.base.Position import prog8.ast.base.*
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.*
import prog8.ast.expressions.StringLiteralValue import prog8.ast.statements.*
import prog8.compiler.* 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.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5 import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import java.io.CharConversionException import java.io.CharConversionException
import java.nio.file.Path
import kotlin.test.* import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -123,13 +126,13 @@ class TestCompiler {
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestZeropage { class TestC64Zeropage {
private val errors = ErrorReporter() private val errors = ErrorReporter()
@Test @Test
fun testNames() { fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
zp.allocate("", DataType.UBYTE, null, errors) zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, errors) zp.allocate("", DataType.UBYTE, null, errors)
@ -142,37 +145,37 @@ class TestZeropage {
@Test @Test
fun testZpFloatEnable() { fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors) zp.allocate("", DataType.FLOAT, null, errors)
} }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors) zp2.allocate("", DataType.FLOAT, null, errors)
} }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true)) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
zp3.allocate("", DataType.FLOAT, null, errors) zp3.allocate("", DataType.FLOAT, null, errors)
} }
@Test @Test
fun testZpModesWithFloats() { fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false))
} }
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false))
} }
} }
@Test @Test
fun testZpDontuse() { fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false))
println(zp.free) println(zp.free)
assertEquals(0, zp.available()) assertEquals(0, zp.available())
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
@ -182,19 +185,19 @@ class TestZeropage {
@Test @Test
fun testFreeSpaces() { fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true)) val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp1.available()) assertEquals(16, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
assertEquals(91, zp2.available()) assertEquals(89, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false)) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
assertEquals(125, zp3.available()) assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false)) val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertEquals(238, zp4.available()) assertEquals(238, zp4.available())
} }
@Test @Test
fun testReservedSpace() { fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false)) val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertEquals(238, zp1.available()) assertEquals(238, zp1.available())
assertTrue(50 in zp1.free) assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free) assertTrue(100 in zp1.free)
@ -203,7 +206,7 @@ class TestZeropage {
assertTrue(200 in zp1.free) assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free) assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free) assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false))
assertEquals(139, zp2.available()) assertEquals(139, zp2.available())
assertFalse(50 in zp2.free) assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free) assertFalse(100 in zp2.free)
@ -216,7 +219,7 @@ class TestZeropage {
@Test @Test
fun testBasicsafeAllocation() { fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp.available()) assertEquals(16, zp.available())
assertFailsWith<ZeropageDepletedError> { assertFailsWith<ZeropageDepletedError> {
@ -239,7 +242,7 @@ class TestZeropage {
@Test @Test
fun testFullAllocation() { fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
assertEquals(238, zp.available()) assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.UWORD, null, errors) val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3) assertTrue(loc > 3)
@ -269,7 +272,7 @@ class TestZeropage {
@Test @Test
fun testEfficientAllocation() { fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
assertEquals(16, zp.available()) assertEquals(16, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors)) assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors)) assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
@ -379,3 +382,169 @@ class TestPetscii {
assertFalse(abc!=abc) assertFalse(abc!=abc)
} }
} }
class TestMemory {
@Test
fun testInValidRamC64_memory_addresses() {
CompilationTarget.instance = C64Target
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
}
@Test
fun testNotInValidRamC64_memory_addresses() {
CompilationTarget.instance = C64Target
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
}
@Test
fun testInValidRamC64_memory_identifiers() {
CompilationTarget.instance = C64Target
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
assertTrue(target.isInRegularRAM(target.definingScope()))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
assertFalse(target.isInRegularRAM(target.definingScope()))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
assertTrue(target.isInRegularRAM(target.definingScope()))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
assertFalse(target.isInRegularRAM(target.definingScope()))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
assertFalse(target.isInRegularRAM(target.definingScope()))
}
@Test
private fun createTestProgramForMemoryRefViaVar(address: Int, vartype: VarDeclType): AssignTarget {
val decl = VarDecl(vartype, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
return target
}
@Test
fun testInValidRamC64_memory_expression() {
CompilationTarget.instance = C64Target
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
}
@Test
fun testInValidRamC64_variable() {
CompilationTarget.instance = C64Target
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@Test
fun testInValidRamC64_memmap_variable() {
CompilationTarget.instance = C64Target
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@Test
fun testNotInValidRamC64_memmap_variable() {
CompilationTarget.instance = C64Target
val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertFalse(target.isInRegularRAM(target.definingScope()))
}
@Test
fun testInValidRamC64_array() {
CompilationTarget.instance = C64Target
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@Test
fun testInValidRamC64_array_memmapped() {
CompilationTarget.instance = C64Target
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@Test
fun testNotValidRamC64_array_memmapped() {
CompilationTarget.instance = C64Target
val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertFalse(target.isInRegularRAM(target.definingScope()))
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -83,7 +83,7 @@ For normal use the compiler is invoked with the command:
By default, assembly code is generated and written to ``sourcefile.asm``. By default, assembly code is generated and written to ``sourcefile.asm``.
It is then (automatically) fed to the `64tass <https://sourceforge.net/projects/tass64/>`_ cross assembler tool It is then (automatically) fed to the `64tass <https://sourceforge.net/projects/tass64/>`_ cross assembler tool
that assembles it into the final program. that assembles it into the final program.
If you use the option to let the compiler auto-start a C-64 emulator, it will do so after If you use the option to let the compiler auto-start an emulator, it will do so after
a successful compilation. This will load your program and the symbol and breakpoint lists a successful compilation. This will load your program and the symbol and breakpoint lists
(for the machine code monitor) into the emulator. (for the machine code monitor) into the emulator.
@ -109,8 +109,8 @@ A module source file is a text file with the ``.p8`` suffix, containing the prog
It consists of compilation options and other directives, imports of other modules, It consists of compilation options and other directives, imports of other modules,
and source code for one or more code blocks. and source code for one or more code blocks.
Prog8 has a couple of *LIBRARY* modules that are defined in special internal files provided by the compiler: Prog8 has various *LIBRARY* modules that are defined in special internal files provided by the compiler.
``c64lib``, ``c64utils``, ``c64flt`` and ``prog8lib``. You should not overwrite these or reuse their names. You should not overwrite these or reuse their names.
They are embedded into the packaged release version of the compiler so you don't have to worry about They are embedded into the packaged release version of the compiler so you don't have to worry about
where they are, but their names are still reserved. where they are, but their names are still reserved.
@ -149,10 +149,10 @@ If your running program hits one of the breakpoints, Vice will halt execution an
Troubleshooting Troubleshooting
--------------- ---------------
Getting an assembler error about undefined symbols such as ``not defined 'c64flt'``? 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 ``c64flt`` library. 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. 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 Examples

View File

@ -12,6 +12,7 @@ What is Prog8?
This is an experimental compiled programming language targeting the 8-bit This is an experimental compiled programming language targeting the 8-bit
`6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ / `6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ /
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ microprocessor. `6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ microprocessor.
This CPU is from the late 1970's and early 1980's and was used in many home computers from that era, This CPU is from the late 1970's and early 1980's and was used in many home computers from that era,
such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_. such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
@ -37,49 +38,49 @@ This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/license
:alt: Fully playable tetris clone :alt: Fully playable tetris clone
Code examples Code example
------------- ------------
This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64utils %import textio
%zeropage basicsafe %zeropage basicsafe
main { main {
ubyte[256] sieve ubyte[256] sieve
ubyte candidate_prime = 2 ubyte candidate_prime = 2 ; is increased in the loop
sub start() { sub start() {
; clear the sieve, to reset starting situation on subsequent runs
memset(sieve, 256, false) memset(sieve, 256, false)
; calculate primes
c64scr.print("prime numbers up to 255:\n\n") txt.print("prime numbers up to 255:\n\n")
ubyte amount=0 ubyte amount=0
while true { repeat {
ubyte prime = find_next_prime() ubyte prime = find_next_prime()
if prime==0 if prime==0
break break
c64scr.print_ub(prime) txt.print_ub(prime)
c64scr.print(", ") txt.print(", ")
amount++ amount++
} }
c64.CHROUT('\n') txt.chrout('\n')
c64scr.print("number of primes (expected 54): ") txt.print("number of primes (expected 54): ")
c64scr.print_ub(amount) txt.print_ub(amount)
c64.CHROUT('\n') txt.chrout('\n')
} }
sub find_next_prime() -> ubyte { sub find_next_prime() -> ubyte {
while sieve[candidate_prime] { while sieve[candidate_prime] {
candidate_prime++ candidate_prime++
if candidate_prime==0 if candidate_prime==0
return 0 return 0 ; we wrapped; no more primes available in the sieve
} }
; found next one, mark the multiples and return it.
sieve[candidate_prime] = true sieve[candidate_prime] = true
uword multiple = candidate_prime uword multiple = candidate_prime
while multiple < len(sieve) { while multiple < len(sieve) {
sieve[lsb(multiple)] = true sieve[lsb(multiple)] = true
multiple += candidate_prime multiple += candidate_prime
@ -96,39 +97,6 @@ when compiled an ran on a C-64 you get this:
:alt: result when run on C-64 :alt: result when run on C-64
The following programs shows a use of the high level ``struct`` type::
%import c64utils
%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
c64scr.print_ub(other.red)
c64.CHROUT(',')
c64scr.print_ub(other.green)
c64.CHROUT(',')
c64scr.print_ub(other.blue)
c64.CHROUT('\n')
}
}
when compiled and ran, it prints ``127,10,99`` on the screen.
Design principles and features Design principles and features
------------------------------ ------------------------------
@ -139,8 +107,8 @@ Design principles and features
- 'One statement per line' code, resulting in clear readable programs. - 'One statement per line' code, resulting in clear readable programs.
- Modular programming and scoping via modules, code blocks, and subroutines. - Modular programming and scoping via modules, code blocks, and subroutines.
- Provide high level programming constructs but at the same time stay close to the metal; - Provide high level programming constructs but at the same time stay close to the metal;
still able to directly use memory addresses, CPU registers and ROM subroutines, still able to directly use memory addresses and ROM subroutines,
and inline assembly to have full control when every cycle or byte matters and inline assembly to have full control when every register, cycle or byte matters
- Arbitrary number of subroutine parameters - Arbitrary number of subroutine parameters
- Complex nested expressions are possible - Complex nested expressions are possible
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters - Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
@ -158,6 +126,7 @@ Design principles and features
the ability to easily write embedded assembly code directly in the program source code. 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) - 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. - 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: .. _requirements:
@ -175,6 +144,7 @@ Fnd for Windows it's possible to get that as well. Check out `AdoptOpenJDK <http
Finally: a **C-64 emulator** (or a real C-64 ofcourse) can be nice to test and run your programs on. Finally: a **C-64 emulator** (or a real C-64 ofcourse) can be nice to test and run your programs on.
The compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_. The compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
If you're targeting the CommanderX16, there's the `x16emu <https://github.com/commanderx16/x16-emulator>`_.
.. important:: .. important::
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*) **Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)

View File

@ -50,7 +50,7 @@ Code
There are different kinds of instructions ('statements' is a better name) such as: There are different kinds of instructions ('statements' is a better name) such as:
- value assignment - value assignment
- looping (for, while, repeat, unconditional jumps) - looping (for, while, do-until, repeat, unconditional jumps)
- conditional execution (if - then - else, when, and conditional jumps) - conditional execution (if - then - else, when, and conditional jumps)
- subroutine calls - subroutine calls
- label definition - label definition
@ -137,7 +137,7 @@ Scopes are created using either of these two statements:
.. important:: .. important::
Unlike most other programming languages, a new scope is *not* created inside Unlike most other programming languages, a new scope is *not* created inside
for, while and repeat statements, the if statement, and the branching conditionals. for, while, repeat, and do-until statements, the if statement, and the branching conditionals.
These all share the same scope from the subroutine they're defined in. These all share the same scope from the subroutine they're defined in.
You can define variables in these blocks, but these will be treated as if they You can define variables in these blocks, but these will be treated as if they
were defined in the subroutine instead. were defined in the subroutine instead.
@ -204,13 +204,6 @@ Example::
byte @zp zeropageCounter = 42 byte @zp zeropageCounter = 42
Variables that represent CPU hardware registers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following variables are reserved
and map directly (read/write) to a CPU hardware register: ``A``, ``X``, ``Y``.
Integers Integers
^^^^^^^^ ^^^^^^^^
@ -233,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) So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM)
are banked in. 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. 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 (this library contains the directive to enable floating points, you don't have
to worry about this yourself) to worry about this yourself)
@ -243,12 +236,15 @@ The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (ne
Arrays 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[10] array ; array of 10 bytes, initially set to 0
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value 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[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] 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) value = array[3] ; the fourth value in the array (index is 0-based)
char = string[4] ; the fifth character (=byte) in the string char = string[4] ; the fifth character (=byte) in the string
@ -264,6 +260,16 @@ Note that the various keywords for the data type and variable type (``byte``, ``
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte`` can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance. for instance.
**Arrays at a specific memory location:**
Using the memory-mapped syntax it is possible to define an array to be located at a specific memory location.
For instance to reference the first 5 rows of the Commodore 64's screen matrix as an array, you can define::
&ubyte[5*40] top5screenrows = $0400
This way you can set the second character on the second row from the top like this::
top5screenrows[41] = '!'
Strings Strings
^^^^^^^ ^^^^^^^
@ -290,6 +296,12 @@ large enough to contain the new value::
string1 = "new value" string1 = "new value"
.. info::
Strings and uwords (=memory address) can often be interchanged.
An array of strings is actually an array of uwords where every element is the memory
address of the string. You can pass a memory address to assembly functions
that require a string as an argument.
.. caution:: .. caution::
It's probably best to avoid changing strings after they've been created. This 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 includes changing certain letters by index, or by assigning a new value, or by
@ -324,7 +336,7 @@ and then create a variable with it::
ubyte blue 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 Color another ; the init value is optional, like arrays
another = rgb ; assign all of the values of rgb to another another = rgb ; assign all of the values of rgb to another
@ -393,21 +405,21 @@ expected when the program is restarted.
Loops Loops
----- -----
The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this. The *for*-loop is used to let a variable iterate over a range of values. Iteration is done in steps of 1, but you can change this.
The loop variable must be declared as byte or word earlier so you can reuse it for multiple occasions. The loop variable must be declared as byte or word earlier so you can reuse it for multiple occasions.
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead. Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
The *while*-loop is used to repeat a piece of code while a certain condition is still true. The *while*-loop is used to repeat a piece of code while a certain condition is still true.
The *repeat--until* loop is used to repeat a piece of code until a certain condition is true. The *do--until* loop is used to repeat a piece of code until a certain condition is true.
The *repeat* loop is used as a short notation of a for loop where the loop variable doesn't matter and you're only interested in the number of iterations.
The *forever*-loop is used to simply run a piece of code in a loop, forever. You can still (without iteration count specified it simply loops forever).
break out of this loop if desired. A "while true" or "until false" loop is equivalent to
a forever-loop.
You can also create loops by using the ``goto`` statement, but this should usually be avoided. You can also create loops by using the ``goto`` statement, but this should usually be avoided.
Breaking out of a loop prematurely is possible with the ``break`` statement.
.. attention:: .. attention::
The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately The value of the loop variable after executing the loop *is undefined*. Don't use it immediately
after the loop without first assigning a new value to it! after the loop without first assigning a new value to it!
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value) (this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
@ -421,15 +433,15 @@ if statements
Conditional execution means that the flow of execution changes based on certiain conditions, Conditional execution means that the flow of execution changes based on certiain conditions,
rather than having fixed gotos or subroutine calls:: rather than having fixed gotos or subroutine calls::
if A>4 goto overflow if aa>4 goto overflow
if X==3 Y = 4 if xx==3 yy = 4
if X==3 Y = 4 else A = 2 if xx==3 yy = 4 else aa = 2
if X==5 { if xx==5 {
Y = 99 yy = 99
} else { } else {
A = 3 aa = 3
} }
@ -493,16 +505,16 @@ Assignments
----------- -----------
Assignment statements assign a single value to a target variable or memory location. Assignment statements assign a single value to a target variable or memory location.
Augmented assignments (such as ``A += X``) are also available, but these are just shorthands Augmented assignments (such as ``aa += xx``) are also available, but these are just shorthands
for normal assignments (``A = A + X``). for normal assignments (``aa = aa + xx``).
Only register variables and variables of type byte, word and float can be assigned a new value. Only variables of type byte, word and float can be assigned a new value.
It's not possible to set a new value to string or array variables etc, because they get allocated It's not possible to set a new value to string or array variables etc, because they get allocated
a fixed amount of memory which will not change. a fixed amount of memory which will not change. (You *can* change the value of elements in a string or array though).
.. attention:: .. attention::
**Data type conversion (in assignments):** **Data type conversion (in assignments):**
When assigning a value with a 'smaller' datatype to a register or variable with a 'larger' datatype, When assigning a value with a 'smaller' datatype to variable with a 'larger' datatype,
the value will be automatically converted to the target datatype: byte --> word --> float. the value will be automatically converted to the target datatype: byte --> word --> float.
So assigning a byte to a word variable, or a word to a floating point variable, is fine. So assigning a byte to a word variable, or a word to a floating point variable, is fine.
The reverse is *not* true: it is *not* possible to assign a value of a 'larger' datatype to The reverse is *not* true: it is *not* possible to assign a value of a 'larger' datatype to
@ -518,7 +530,7 @@ as the memory mapped address $d021.
If you want to access a memory location directly (by using the address itself), without defining If you want to access a memory location directly (by using the address itself), without defining
a memory mapped location, you can do so by enclosing the address in ``@(...)``:: a memory mapped location, you can do so by enclosing the address in ``@(...)``::
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)") color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0") @($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
@(vic+$20) = 6 ; you can also use expressions to 'calculate' the address @(vic+$20) = 6 ; you can also use expressions to 'calculate' the address
@ -575,25 +587,24 @@ within parentheses will be evaluated first. So ``(4 + 8) * 2`` is 24 and not 20,
and ``(true or false) and false`` is false instead of true. and ``(true or false) and false`` is false instead of true.
.. attention:: .. 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 BYTE type, the result will remain a BYTE.
When you do calculations on a WORD type, the result will remain a WORD. When you do calculations on a WORD type, the result will remain a WORD.
For instance:: For instance::
byte b = 44 byte b = 44
word w = b*55 ; the result will be 116! (even though the target variable is a word) 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) performance reasons - so you won't get sudden 16 bit (or even float)
calculations where you needed only simple fast byte arithmetic. calculations where you needed only simple fast byte arithmetic.
If you do need the extended resulting value, cast at least one of the 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 byte b = 44
word w = b*55.w ; the result will be 2420 w = (b as word)*55
w = (b as word)*55 ; same result w = b*(55 as word)
@ -716,16 +727,22 @@ sort(array)
floating point values. floating point values.
reverse(array) reverse(array)
Reverse the values in the array (in-place). Supports all data types including floats. Reverse the values in the array (in-place).
Can be used after sort() to sort an array in descending order. Can be used after sort() to sort an array in descending order.
len(x) len(x)
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte). Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. See sizeof().
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
length of the string during execution, the value of len(string) may no longer be correct! 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) (use strlen function if you want to dynamically determine the length)
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
strlen(str) strlen(str)
Number of bytes in the string. This value is determined during runtime and counts upto 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. the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
@ -739,8 +756,9 @@ msb(x)
sgn(x) sgn(x)
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive). Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
mkword(lsb, msb) mkword(msb, lsb)
Efficiently create a word value from two bytes (the lsb and the msb). Avoids multiplication and shifting. 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) any(x)
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false') 1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
@ -757,16 +775,6 @@ rndw()
rndf() rndf()
returns a pseudo-random float between 0.0 and 1.0 returns a pseudo-random float between 0.0 and 1.0
lsl(x)
Shift the bits in x (byte or word) one position to the left.
Bit 0 is set to 0 (and the highest bit is shifted into the status register's Carry flag)
Modifies in-place, doesn't return a value (so can't be used in an expression).
lsr(x)
Shift the bits in x (byte or word) one position to the right.
The highest bit is set to 0 (and bit 0 is shifted into the status register's Carry flag)
Modifies in-place, doesn't return a value (so can't be used in an expression).
rol(x) rol(x)
Rotate the bits in x (byte or word) one position to the left. Rotate the bits in x (byte or word) one position to the left.
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag, This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
@ -803,7 +811,7 @@ memset(address, numbytes, bytevalue)
Efficiently set a part of memory to the given (u)byte value. 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! 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 Note that for clearing the character screen, very fast specialized subroutines are
available in the ``c64scr`` block (part of the ``c64utils`` module) available in the ``txt`` block (part of the ``textio`` module)
memsetw(address, numwords, wordvalue) memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value. Efficiently set a part of memory to the given (u)word value.

View File

@ -24,7 +24,7 @@ Everything after a semicolon ``;`` is a comment and is ignored.
If the whole line is just a comment, it will be copied into the resulting assembly source code. If the whole line is just a comment, it will be copied into the resulting assembly source code.
This makes it easier to understand and relate the generated code. Examples:: This makes it easier to understand and relate the generated code. Examples::
A = 42 ; set the initial value to 42 counter = 42 ; set the initial value to 42
; next is the code that... ; next is the code that...
@ -33,6 +33,13 @@ This makes it easier to understand and relate the generated code. Examples::
Directives Directives
----------- -----------
.. data:: %target <target>
Level: module.
Global setting, specifies that this module can only work for the given compiler target.
If compiled with a different target, compilation is aborted with an error message.
.. data:: %output <type> .. data:: %output <type>
Level: module. Level: module.
@ -60,7 +67,8 @@ Directives
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines, - 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), 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. 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 - 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. 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 - style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
@ -71,9 +79,10 @@ Directives
except the few addresses mentioned above that are used by the system's IRQ routine. 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. 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``. This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
This option makes programs smaller and faster because even more variables can This option makes programs smaller and faster because even more variables can
be stored in the ZP (which allows for more efficient assembly code). 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. - style ``dontuse`` -- don't use *any* location in the zeropage.
Also read :ref:`zeropage`. Also read :ref:`zeropage`.
@ -110,33 +119,38 @@ Directives
Level: module, block. Level: module, block.
Sets special compiler options. 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). - For a module option, there is ``enable_floats``, which will tell the compiler
Otherwise, floating point support is not enabled. to deal with floating point numbers (by using various subroutines from the Commodore-64 kernal).
When used in a block with the ``force_output`` option, it will force the block to be outputted Otherwise, floating point support is not enabled.
in the final program. Can be useful to make sure some - There's also ``no_sysinit`` which cause the resulting program to *not* include
data is generated that would otherwise be discarded because it's not referenced (such as sprite data). 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>]] .. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
Level: block. Level: block.
This directive can only be used inside a 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 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 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! The file is located relative to the current working directory!
.. data:: %asminclude "<filename>", "scopelabel" .. data:: %asminclude "<filename>", "scopelabel"
Level: block. Level: block.
This directive can only be used inside a block. This directive can only be used inside a block.
The assembler will include the file as raw assembly source text at this point, 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. 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, 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. 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. 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, 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. if the file can't be found there it is searched relative to the current directory.
.. data:: %breakpoint .. data:: %breakpoint
@ -157,7 +171,7 @@ Directives
Identifiers Identifiers
----------- -----------
Naming things in Prog8 is done via valid *identifiers*. They start with a letter or underscore, Naming things in Prog8 is done via valid *identifiers*. They start with a letter,
and after that, a combination of letters, numbers, or underscores. Examples of valid identifiers:: and after that, a combination of letters, numbers, or underscores. Examples of valid identifiers::
a a
@ -165,7 +179,7 @@ and after that, a combination of letters, numbers, or underscores. Examples of v
monkey monkey
COUNTER COUNTER
Better_Name_2 Better_Name_2
_something_strange_ something_strange__
Code blocks Code blocks
@ -267,6 +281,7 @@ type identifier type storage size example var declara
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]`` ``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]`` ``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]`` ``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."`` ``str`` string (petscii) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte implicitly terminated by a 0-byte
=============== ======================= ================= ========================================= =============== ======================= ================= =========================================
@ -289,7 +304,8 @@ of something with an operand starting with 1 or 0, you'll have to add a space in
- When an integer value ranges from 256..65535 the compiler sees it as a ``uword``. For -32768..32767 it's a ``word``. - When 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 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``. - 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 Data type conversion
@ -306,6 +322,7 @@ should be allocated by the compiler. Instead, the (mandatory) value assigned to
should be the *memory address* where the value is located:: should be the *memory address* where the value is located::
&byte BORDERCOLOR = $d020 &byte BORDERCOLOR = $d020
&ubyte[5*40] top5screenrows = $0400 ; works for array as well
Direct access to memory locations Direct access to memory locations
@ -313,7 +330,7 @@ Direct access to memory locations
Instead of defining a memory mapped name for a specific memory location, you can also Instead of defining a memory mapped name for a specific memory location, you can also
directly access the memory. Enclose a numeric expression or literal with ``@(...)`` to do that:: directly access the memory. Enclose a numeric expression or literal with ``@(...)`` to do that::
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)") color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0") @($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
@(vic+$20) = 6 ; a dynamic expression to 'calculate' the address @(vic+$20) = 6 ; a dynamic expression to 'calculate' the address
@ -333,8 +350,6 @@ Reserved names
The following names are reserved, they have a special meaning:: The following names are reserved, they have a special meaning::
A X Y ; 6502 hardware registers
Pc Pz Pn Pv ; 6502 status register flags
true false ; boolean values 1 and 0 true false ; boolean values 1 and 0
@ -384,7 +399,7 @@ After defining a struct you can use the name of the struct as a data type to dec
Struct variables can be assigned a struct literal value (also in their declaration as initial value):: Struct variables can be assigned a struct literal value (also in their declaration as initial value)::
Color rgb = {255, 100, 0} ; curly braces instead of brackets Color rgb = [255, 100, 0] ; note that the value is an array
Operators Operators
@ -406,10 +421,10 @@ assignment: ``=``
Note that an assignment sometimes is not possible or supported. Note that an assignment sometimes is not possible or supported.
augmented assignment: ``+=`` ``-=`` ``*=`` ``/=`` ``**=`` ``&=`` ``|=`` ``^=`` ``<<=`` ``>>=`` augmented assignment: ``+=`` ``-=`` ``*=`` ``/=`` ``**=`` ``&=`` ``|=`` ``^=`` ``<<=`` ``>>=``
Syntactic sugar; ``A += X`` is equivalent to ``A = A + X`` This is syntactic sugar; ``aa += xx`` is equivalent to ``aa = aa + xx``
postfix increment and decrement: ``++`` ``--`` postfix increment and decrement: ``++`` ``--``
Syntactic sugar; ``A++`` is equivalent to ``A = A + 1``, and ``A--`` is equivalent to ``A = A - 1``. Syntactic sugar; ``aa++`` is equivalent to ``aa = aa + 1``, and ``aa--`` is equivalent to ``aa = aa - 1``.
Because these operations are so common, we have these short forms. Because these operations are so common, we have these short forms.
comparison: ``!=`` ``<`` ``>`` ``<=`` ``>=`` comparison: ``!=`` ``<`` ``>`` ``<=`` ``>=``
@ -427,9 +442,9 @@ range creation: ``to``
0 to 7 ; range of values 0, 1, 2, 3, 4, 5, 6, 7 (constant) 0 to 7 ; range of values 0, 1, 2, 3, 4, 5, 6, 7 (constant)
A = 5 aa = 5
X = 10 aa = 10
A to X ; range of 5, 6, 7, 8, 9, 10 aa to xx ; range of 5, 6, 7, 8, 9, 10
byte[] array = 10 to 13 ; sets the array to [1, 2, 3, 4] byte[] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
@ -515,18 +530,20 @@ and returning stuff in several registers as well. The ``clobbers`` clause is use
what CPU registers are clobbered by the call instead of being unchanged or returning a meaningful result value. what CPU registers are clobbered by the call instead of being unchanged or returning a meaningful result value.
Subroutines that are implemented purely in assembly code and which have an assembly calling convention (i.e. User subroutines in the program source code that are implemented purely in assembly and which have an assembly calling convention (i.e.
the parameters are strictly passed via cpu registers), are defined like this:: the parameters are strictly passed via cpu registers), are defined with ``asmsub`` like this::
asmsub FREADS32() clobbers(A,X,Y) { asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
%asm {{ %asm {{
lda $62 ldy #0
eor #$ff _loop sta c64.Screen,y
asl a sta c64.Screen+$0100,y
lda #0 sta c64.Screen+$0200,y
ldx #$a0 sta c64.Screen+$02e8,y
jmp $bc4f iny
}} bne _loop
rts
}}
} }
the statement body of such a subroutine should consist of just an inline assembly block. the statement body of such a subroutine should consist of just an inline assembly block.
@ -551,7 +568,7 @@ Loops
for loop for loop
^^^^^^^^ ^^^^^^^^
The loop variable must be a register or a byte/word variable, The loop variable must be a byte or word variable,
and must be defined first in the local scope of the for loop. and must be defined first in the local scope of the for loop.
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``, The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported). array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
@ -561,7 +578,6 @@ You can use a single statement, or a statement block like in the example below::
for <loopvar> in <expression> [ step <amount> ] { for <loopvar> in <expression> [ step <amount> ] {
; do something... ; do something...
break ; break out of the loop break ; break out of the loop
continue ; immediately enter next iteration
} }
For example, this is a for loop using a byte variable ``i``, defined before, to loop over a certain range of numbers:: For example, this is a for loop using a byte variable ``i``, defined before, to loop over a certain range of numbers::
@ -594,35 +610,35 @@ You can use a single statement, or a statement block like in the example below::
while <condition> { while <condition> {
; do something... ; do something...
break ; break out of the loop break ; break out of the loop
continue ; immediately enter next iteration
} }
repeat-until loop do-until loop
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
Until the given condition is true (1), repeat the given statement(s). Until the given condition is true (1), repeat the given statement(s).
You can use a single statement, or a statement block like in the example below:: You can use a single statement, or a statement block like in the example below::
repeat { do {
; do something... ; do something...
break ; break out of the loop break ; break out of the loop
continue ; immediately enter next iteration
} until <condition> } until <condition>
forever loop repeat loop
^^^^^^^^^^^^ ^^^^^^^^^^^
Simply run the code in a loop, forever. It's the same as a while true or until false loop, When you're only interested in repeating something a given number of times.
or just a jump back to a previous label. You can still break out of this loop as well, if you want:: It's a short hand for a for loop without an explicit loop variable::
forever { repeat 15 {
; .. do stuff ; do something...
if something break ; you can break out of the loop
break ; you can exit the loop if you want
} }
If you omit the iteration count, it simply loops forever.
You can still ``break`` out of such a loop if you want though.
Conditional Execution and Jumps Conditional Execution and Jumps
------------------------------- -------------------------------
@ -702,3 +718,4 @@ case you have to use { } to enclose them::
} }
else -> c64scr.print("don't know") else -> c64scr.print("don't know")
} }

View File

@ -4,12 +4,20 @@ Target system specification
Prog8 targets the following hardware: Prog8 targets the following hardware:
- 8 bit MOS 6502/6510 CPU - 8 bit MOS 6502/65c02/6510 CPU
- 64 Kb addressable memory (RAM or ROM) - 64 Kb addressable memory (RAM or ROM)
- memory mapped I/O registers - optional use of memory mapped I/O registers
- optional use of system ROM routines
The main target machine is the Commodore-64, which is an example of this. Currently there are two machines that are supported as compiler target (selectable via the ``-target`` compiler argument):
This chapter explains the relevant system details of such a machine.
- 'c64': the well-known Commodore-64, premium support
- 'cx16': the `CommanderX16 <https://www.commanderx16.com/>`_ a project from the 8-Bit Guy. Support for this is still experimental.
This chapter explains the relevant system details of these machines.
.. note::
If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
Memory Model Memory Model
@ -113,22 +121,14 @@ CPU
Directly Usable Registers Directly Usable Registers
------------------------- -------------------------
The following 6502 CPU hardware registers are directly usable in program code (and are reserved symbols): The hardware CPU registers are not directly accessible from regular Prog8 code.
If you need to mess with them, you'll have to use inline assembly.
Be extra wary of the ``X`` register because it is used as an evaluation stack pointer and
changing its value you will destroy the evaluation stack and likely crash the program.
- ``A``, ``X``, ``Y`` the three main cpu registers (8 bits) The status register (P) carry flag and interrupt disable flag can be written via a couple of special
- the status register (P) carry flag and interrupt disable flag can be written via a couple of special builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``), and read via the ``read_flags()`` function.
and read via the ``read_flags()`` function.
However, you must assume that the 3 hardware registers ``A``, ``X`` and ``Y``
are volatile. Their values cannot be depended upon, the compiler will use them as required.
Even simple assignments may require modification of one or more of the registers (for instance, when using arrays).
Even more important, the ``X`` register is used as an evaluation stack pointer.
If you mess with it, you will destroy the evaluation stack and likely crash your program.
In some cases the compiler will warn you about this, but you should really avoid to use
this register. It's possible to store/restore the register's value (using special built in functions)
for the cases you really really need to use it directly.
Subroutine Calling Conventions Subroutine Calling Conventions
@ -155,15 +155,15 @@ You can however install your own IRQ handler.
This is possible ofcourse by doing it all using customized inline assembly, This is possible ofcourse by doing it all using customized inline assembly,
but there are a few library routines available to make setting up C-64 IRQs and raster IRQs a lot easier (no assembly code required). but there are a few library routines available to make setting up C-64 IRQs and raster IRQs a lot easier (no assembly code required).
These routines are:: For the C64 these routines are::
c64utils.set_irqvec() c64.set_irqvec()
c64utils.set_irqvec_excl() c64.set_irqvec_excl()
c64utils.set_rasterirq( <raster line> ) c64.set_rasterirq( <raster line> )
c64utils.set_rasterirq_excl( <raster line> ) c64.set_rasterirq_excl( <raster line> )
c64utils.restore_irqvec() ; set it back to the systems default irq handler c64.restore_irqvec() ; set it back to the systems default irq handler
If you activate an IRQ handler with one of these, it expects the handler to be defined If you activate an IRQ handler with one of these, it expects the handler to be defined
as a subroutine ``irq`` in the module ``irq`` so like this:: as a subroutine ``irq`` in the module ``irq`` so like this::
@ -173,3 +173,4 @@ as a subroutine ``irq`` in the module ``irq`` so like this::
; ... irq handling here ... ; ... irq handling here ...
} }
} }

View File

@ -2,31 +2,28 @@
TODO TODO
==== ====
- BUG FIX: fix register argument clobbering when calling asmsubs. (see fixme_argclobber.p8) - get rid of all other TODO's in the code ;-)
- make it possible for array literals to not only contain compile time constants?
- implement @stack for asmsub parameters
- finalize (most) of the still missing "new" assignment asm code generation - make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
- aliases for imported symbols for example perhaps '%alias print = c64scr.print' - option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
- option to load library files from a directory instead of the embedded ones (easier library development/debugging)
- investigate support for 8bitguy's Commander X16 platform https://www.commanderx16.com and https://github.com/commanderx16/x16-docs
- see if we can group some errors together for instance the (now single) errors about unidentified symbols - 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 More optimizations
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
Add more compiler optimizations to the existing ones. Add more compiler optimizations to the existing ones.
- more targeted optimizations for assigment asm code, such as the following: - further optimize assignment codegeneration, such as the following:
- binexpr splitting (beware self-referencing expressions and asm code ballooning though)
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise. - 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 - can such parameter passing to subroutines be optimized to avoid copying?
- working subroutine inlining (start with trivial routines, grow to taking care of vars and identifier refs to them)
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
the program will then rely solely on the values as they are in memory at the time of program startup.
- Also some library routines and code patterns could perhaps be optimized further
- can the parameter passing to subroutines be optimized to avoid copying?
- more optimizations on the language AST level - more optimizations on the language AST level
- more optimizations on the final assembly source level - more optimizations on the final assembly source level
- note: subroutine inlining is abandoned because of problems referencing non-local stuff. Can't move everything around.
Eval stack redesign? (lot of work) Eval stack redesign? (lot of work)

View File

@ -1,7 +1,6 @@
%import c64lib %import floats
%import c64utils %import textio
%import c64flt %zeropage basicsafe
%zeropage dontuse
main { main {
@ -20,101 +19,90 @@ main {
; LEN/STRLEN ; LEN/STRLEN
ubyte length = len(name) ubyte length = len(name)
if length!=5 c64scr.print("error len1\n") if length!=5 txt.print("error len1\n")
length = len(uwarr) length = len(uwarr)
if length!=5 c64scr.print("error len2\n") if length!=5 txt.print("error len2\n")
length=strlen(name) length=strlen(name)
if length!=5 c64scr.print("error strlen1\n") if length!=5 txt.print("error strlen1\n")
name[3] = 0 name[3] = 0
length=strlen(name) length=strlen(name)
if length!=3 c64scr.print("error strlen2\n") if length!=3 txt.print("error strlen2\n")
; MAX ; MAX
ub = max(ubarr) ub = max(ubarr)
if ub!=199 c64scr.print("error max1\n") if ub!=199 txt.print("error max1\n")
bb = max(barr) bb = max(barr)
if bb!=99 c64scr.print("error max2\n") if bb!=99 txt.print("error max2\n")
uw = max(uwarr) uw = max(uwarr)
if uw!=4444 c64scr.print("error max3\n") if uw!=4444 txt.print("error max3\n")
ww = max(warr) ww = max(warr)
if ww!=999 c64scr.print("error max4\n") if ww!=999 txt.print("error max4\n")
ff = max(farr) ff = max(farr)
if ff!=999.9 c64scr.print("error max5\n") if ff!=999.9 txt.print("error max5\n")
; MIN ; MIN
ub = min(ubarr) ub = min(ubarr)
if ub!=0 c64scr.print("error min1\n") if ub!=0 txt.print("error min1\n")
bb = min(barr) bb = min(barr)
if bb!=-122 c64scr.print("error min2\n") if bb!=-122 txt.print("error min2\n")
uw = min(uwarr) uw = min(uwarr)
if uw!=0 c64scr.print("error min3\n") if uw!=0 txt.print("error min3\n")
ww = min(warr) ww = min(warr)
if ww!=-4444 c64scr.print("error min4\n") if ww!=-4444 txt.print("error min4\n")
ff = min(farr) ff = min(farr)
if ff!=-4444.4 c64scr.print("error min5\n") if ff!=-4444.4 txt.print("error min5\n")
; SUM ; SUM
uw = sum(ubarr) uw = sum(ubarr)
if uw!=420 c64scr.print("error sum1\n") if uw!=420 txt.print("error sum1\n")
ww = sum(barr) ww = sum(barr)
if ww!=-101 c64scr.print("error sum2\n") if ww!=-101 txt.print("error sum2\n")
uw = sum(uwarr) uw = sum(uwarr)
if uw!=6665 c64scr.print("error sum3\n") if uw!=6665 txt.print("error sum3\n")
ww = sum(warr) ww = sum(warr)
if ww!=-4223 c64scr.print("error sum4\n") if ww!=-4223 txt.print("error sum4\n")
ff = sum(farr) ff = sum(farr)
if ff!=-4222.4 c64scr.print("error sum5\n") if ff!=-4222.4 txt.print("error sum5\n")
; ANY ; ANY
ub = any(ubarr) ub = any(ubarr)
if ub==0 c64scr.print("error any1\n") if ub==0 txt.print("error any1\n")
ub = any(barr) ub = any(barr)
if ub==0 c64scr.print("error any2\n") if ub==0 txt.print("error any2\n")
ub = any(uwarr) ub = any(uwarr)
if ub==0 c64scr.print("error any3\n") if ub==0 txt.print("error any3\n")
ub = any(warr) ub = any(warr)
if ub==0 c64scr.print("error any4\n") if ub==0 txt.print("error any4\n")
ub = any(farr) ub = any(farr)
if ub==0 c64scr.print("error any5\n") if ub==0 txt.print("error any5\n")
; ALL ; ALL
ub = all(ubarr) ub = all(ubarr)
if ub==1 c64scr.print("error all1\n") if ub==1 txt.print("error all1\n")
ub = all(barr) ub = all(barr)
if ub==1 c64scr.print("error all2\n") if ub==1 txt.print("error all2\n")
ub = all(uwarr) ub = all(uwarr)
if ub==1 c64scr.print("error all3\n") if ub==1 txt.print("error all3\n")
ub = all(warr) ub = all(warr)
if ub==1 c64scr.print("error all4\n") if ub==1 txt.print("error all4\n")
ub = all(farr) ub = all(farr)
if ub==1 c64scr.print("error all5\n") if ub==1 txt.print("error all5\n")
ubarr[1]=$40 ubarr[1]=$40
barr[1]=$40 barr[1]=$40
uwarr[1]=$4000 uwarr[1]=$4000
warr[1]=$4000 warr[1]=$4000
farr[1]=1.1 farr[1]=1.1
ub = all(ubarr) ub = all(ubarr)
if ub==0 c64scr.print("error all6\n") if ub==0 txt.print("error all6\n")
ub = all(barr) ub = all(barr)
if ub==0 c64scr.print("error all7\n") if ub==0 txt.print("error all7\n")
ub = all(uwarr) ub = all(uwarr)
if ub==0 c64scr.print("error all8\n") if ub==0 txt.print("error all8\n")
ub = all(warr) ub = all(warr)
if ub==0 c64scr.print("error all9\n") if ub==0 txt.print("error all9\n")
ub = all(farr) ub = all(farr)
if ub==0 c64scr.print("error all10\n") if ub==0 txt.print("error all10\n")
check_eval_stack() txt.print("\nyou should see no errors printed above (only at first run).")
c64scr.print("\nyou should see no errors printed above (only at first run).")
} }
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
} }

View File

@ -1,383 +1,385 @@
%import c64utils %import textio
%zeropage dontuse %zeropage basicsafe
main { main {
sub start() { sub start() {
c64scr.print("ubyte shift left\n") ubyte A
txt.print("ubyte shift left\n")
A = shiftlb0() A = shiftlb0()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftlb1() A = shiftlb1()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftlb2() A = shiftlb2()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftlb3() A = shiftlb3()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftlb4() A = shiftlb4()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftlb5() A = shiftlb5()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftlb6() A = shiftlb6()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftlb7() A = shiftlb7()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftlb8() A = shiftlb8()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftlb9() A = shiftlb9()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.print("enter to continue:\n") txt.print("enter to continue:\n")
void c64.CHRIN() void c64.CHRIN()
c64scr.print("ubyte shift right\n") txt.print("ubyte shift right\n")
A = shiftrb0() A = shiftrb0()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftrb1() A = shiftrb1()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftrb2() A = shiftrb2()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftrb3() A = shiftrb3()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftrb4() A = shiftrb4()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftrb5() A = shiftrb5()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftrb6() A = shiftrb6()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftrb7() A = shiftrb7()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftrb8() A = shiftrb8()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
A = shiftrb9() A = shiftrb9()
c64scr.print_ubbin(A, true) txt.print_ubbin(A, true)
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.print("enter to continue:\n") txt.print("enter to continue:\n")
void c64.CHRIN() void c64.CHRIN()
c64scr.print("signed byte shift left\n") txt.print("signed byte shift left\n")
byte signedb byte signedb
signedb = shiftlsb0() signedb = shiftlsb0()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftlsb1() signedb = shiftlsb1()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftlsb2() signedb = shiftlsb2()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftlsb3() signedb = shiftlsb3()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftlsb4() signedb = shiftlsb4()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftlsb5() signedb = shiftlsb5()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftlsb6() signedb = shiftlsb6()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftlsb7() signedb = shiftlsb7()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftlsb8() signedb = shiftlsb8()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftlsb9() signedb = shiftlsb9()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.print("enter to continue:\n") txt.print("enter to continue:\n")
void c64.CHRIN() void c64.CHRIN()
c64scr.print("signed byte shift right\n") txt.print("signed byte shift right\n")
signedb = shiftrsb0() signedb = shiftrsb0()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftrsb1() signedb = shiftrsb1()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftrsb2() signedb = shiftrsb2()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftrsb3() signedb = shiftrsb3()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftrsb4() signedb = shiftrsb4()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftrsb5() signedb = shiftrsb5()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftrsb6() signedb = shiftrsb6()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftrsb7() signedb = shiftrsb7()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftrsb8() signedb = shiftrsb8()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
signedb = shiftrsb9() signedb = shiftrsb9()
c64scr.print_ubbin(signedb as ubyte, true) txt.print_ubbin(signedb as ubyte, true)
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.print("enter to continue:\n") txt.print("enter to continue:\n")
void c64.CHRIN() void c64.CHRIN()
c64scr.print("uword shift left\n") txt.print("uword shift left\n")
uword uw uword uw
uw = shiftluw0() uw = shiftluw0()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw1() uw = shiftluw1()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw2() uw = shiftluw2()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw3() uw = shiftluw3()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw4() uw = shiftluw4()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw5() uw = shiftluw5()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw6() uw = shiftluw6()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw7() uw = shiftluw7()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw8() uw = shiftluw8()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw9() uw = shiftluw9()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw10() uw = shiftluw10()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw11() uw = shiftluw11()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw12() uw = shiftluw12()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw13() uw = shiftluw13()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw14() uw = shiftluw14()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw15() uw = shiftluw15()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw16() uw = shiftluw16()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftluw17() uw = shiftluw17()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.print("enter to continue:\n") txt.print("enter to continue:\n")
void c64.CHRIN() void c64.CHRIN()
c64scr.print("uword shift right\n") txt.print("uword shift right\n")
uw = shiftruw0() uw = shiftruw0()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw1() uw = shiftruw1()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw2() uw = shiftruw2()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw3() uw = shiftruw3()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw4() uw = shiftruw4()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw5() uw = shiftruw5()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw6() uw = shiftruw6()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw7() uw = shiftruw7()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw8() uw = shiftruw8()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw9() uw = shiftruw9()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw10() uw = shiftruw10()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw11() uw = shiftruw11()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw12() uw = shiftruw12()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw13() uw = shiftruw13()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw14() uw = shiftruw14()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw15() uw = shiftruw15()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw16() uw = shiftruw16()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
uw = shiftruw17() uw = shiftruw17()
c64scr.print_uwbin(uw, true) txt.print_uwbin(uw, true)
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.print("enter to continue:\n") txt.print("enter to continue:\n")
void c64.CHRIN() void c64.CHRIN()
c64scr.print("signed word shift left\n") txt.print("signed word shift left\n")
word sw word sw
sw = shiftlsw0() sw = shiftlsw0()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw1() sw = shiftlsw1()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw2() sw = shiftlsw2()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw3() sw = shiftlsw3()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw4() sw = shiftlsw4()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw5() sw = shiftlsw5()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw6() sw = shiftlsw6()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw7() sw = shiftlsw7()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw8() sw = shiftlsw8()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw9() sw = shiftlsw9()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw10() sw = shiftlsw10()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw11() sw = shiftlsw11()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw12() sw = shiftlsw12()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw13() sw = shiftlsw13()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw14() sw = shiftlsw14()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw15() sw = shiftlsw15()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw16() sw = shiftlsw16()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftlsw17() sw = shiftlsw17()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.print("enter to continue:\n") txt.print("enter to continue:\n")
void c64.CHRIN() void c64.CHRIN()
c64scr.print("signed word shift right\n") txt.print("signed word shift right\n")
sw = shiftrsw0() sw = shiftrsw0()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw1() sw = shiftrsw1()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw2() sw = shiftrsw2()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw3() sw = shiftrsw3()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw4() sw = shiftrsw4()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw5() sw = shiftrsw5()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw6() sw = shiftrsw6()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw7() sw = shiftrsw7()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw8() sw = shiftrsw8()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw9() sw = shiftrsw9()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw10() sw = shiftrsw10()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw11() sw = shiftrsw11()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw12() sw = shiftrsw12()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw13() sw = shiftrsw13()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw14() sw = shiftrsw14()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw15() sw = shiftrsw15()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw16() sw = shiftrsw16()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
sw = shiftrsw17() sw = shiftrsw17()
c64scr.print_uwbin(sw as uword, true) txt.print_uwbin(sw as uword, true)
c64.CHROUT('\n') c64.CHROUT('\n')
} }

View File

@ -1,6 +1,5 @@
%import c64lib %import floats
%import c64utils %import textio
%import c64flt
%zeropage basicsafe %zeropage basicsafe
main { main {
@ -24,91 +23,81 @@ main {
div_float(0,1,0) div_float(0,1,0)
div_float(999.9,111.0,9.008108108108107) div_float(999.9,111.0,9.008108108108107)
check_eval_stack()
} }
sub div_ubyte(ubyte a1, ubyte a2, ubyte c) { sub div_ubyte(ubyte a1, ubyte a2, ubyte c) {
ubyte r = a1/a2 ubyte r = a1/a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("ubyte ") txt.print("ubyte ")
c64scr.print_ub(a1) txt.print_ub(a1)
c64scr.print(" / ") txt.print(" / ")
c64scr.print_ub(a2) txt.print_ub(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_ub(r) txt.print_ub(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub div_byte(byte a1, byte a2, byte c) { sub div_byte(byte a1, byte a2, byte c) {
byte r = a1/a2 byte r = a1/a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("byte ") txt.print("byte ")
c64scr.print_b(a1) txt.print_b(a1)
c64scr.print(" / ") txt.print(" / ")
c64scr.print_b(a2) txt.print_b(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_b(r) txt.print_b(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub div_uword(uword a1, uword a2, uword c) { sub div_uword(uword a1, uword a2, uword c) {
uword r = a1/a2 uword r = a1/a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("uword ") txt.print("uword ")
c64scr.print_uw(a1) txt.print_uw(a1)
c64scr.print(" / ") txt.print(" / ")
c64scr.print_uw(a2) txt.print_uw(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_uw(r) txt.print_uw(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub div_word(word a1, word a2, word c) { sub div_word(word a1, word a2, word c) {
word r = a1/a2 word r = a1/a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("word ") txt.print("word ")
c64scr.print_w(a1) txt.print_w(a1)
c64scr.print(" / ") txt.print(" / ")
c64scr.print_w(a2) txt.print_w(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_w(r) txt.print_w(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub div_float(float a1, float a2, float c) { sub div_float(float a1, float a2, float c) {
float r = a1/a2 float r = a1/a2
if abs(r-c)<0.00001 if abs(r-c)<0.00001
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("float ") txt.print("float ")
c64flt.print_f(a1) floats.print_f(a1)
c64scr.print(" / ") txt.print(" / ")
c64flt.print_f(a2) floats.print_f(a2)
c64scr.print(" = ") txt.print(" = ")
c64flt.print_f(r) floats.print_f(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
} }

View File

@ -1,6 +1,5 @@
%import c64lib %import floats
%import c64utils %import textio
%import c64flt
%zeropage basicsafe %zeropage basicsafe
main { main {
@ -32,92 +31,81 @@ main {
minus_float(0,0,0) minus_float(0,0,0)
minus_float(2.5,1.5,1.0) minus_float(2.5,1.5,1.0)
minus_float(-1.5,3.5,-5.0) minus_float(-1.5,3.5,-5.0)
check_eval_stack()
} }
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) { sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
ubyte r = a1-a2 ubyte r = a1-a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("ubyte ") txt.print("ubyte ")
c64scr.print_ub(a1) txt.print_ub(a1)
c64scr.print(" - ") txt.print(" - ")
c64scr.print_ub(a2) txt.print_ub(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_ub(r) txt.print_ub(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub minus_byte(byte a1, byte a2, byte c) { sub minus_byte(byte a1, byte a2, byte c) {
byte r = a1-a2 byte r = a1-a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("byte ") txt.print("byte ")
c64scr.print_b(a1) txt.print_b(a1)
c64scr.print(" - ") txt.print(" - ")
c64scr.print_b(a2) txt.print_b(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_b(r) txt.print_b(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub minus_uword(uword a1, uword a2, uword c) { sub minus_uword(uword a1, uword a2, uword c) {
uword r = a1-a2 uword r = a1-a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("uword ") txt.print("uword ")
c64scr.print_uw(a1) txt.print_uw(a1)
c64scr.print(" - ") txt.print(" - ")
c64scr.print_uw(a2) txt.print_uw(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_uw(r) txt.print_uw(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub minus_word(word a1, word a2, word c) { sub minus_word(word a1, word a2, word c) {
word r = a1-a2 word r = a1-a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("word ") txt.print("word ")
c64scr.print_w(a1) txt.print_w(a1)
c64scr.print(" - ") txt.print(" - ")
c64scr.print_w(a2) txt.print_w(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_w(r) txt.print_w(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub minus_float(float a1, float a2, float c) { sub minus_float(float a1, float a2, float c) {
float r = a1-a2 float r = a1-a2
if abs(r-c)<0.00001 if abs(r-c)<0.00001
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("float ") txt.print("float ")
c64flt.print_f(a1) floats.print_f(a1)
c64scr.print(" - ") txt.print(" - ")
c64flt.print_f(a2) floats.print_f(a2)
c64scr.print(" = ") txt.print(" = ")
c64flt.print_f(r) floats.print_f(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
} }

View File

@ -1,6 +1,5 @@
%import c64lib %import floats
%import c64utils %import textio
%import c64flt
%zeropage basicsafe %zeropage basicsafe
main { main {
@ -26,91 +25,81 @@ main {
mul_float(0,0,0) mul_float(0,0,0)
mul_float(2.5,10,25) mul_float(2.5,10,25)
mul_float(-1.5,10,-15) mul_float(-1.5,10,-15)
check_eval_stack()
} }
sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) { sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) {
ubyte r = a1*a2 ubyte r = a1*a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("ubyte ") txt.print("ubyte ")
c64scr.print_ub(a1) txt.print_ub(a1)
c64scr.print(" * ") txt.print(" * ")
c64scr.print_ub(a2) txt.print_ub(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_ub(r) txt.print_ub(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub mul_byte(byte a1, byte a2, byte c) { sub mul_byte(byte a1, byte a2, byte c) {
byte r = a1*a2 byte r = a1*a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("byte ") txt.print("byte ")
c64scr.print_b(a1) txt.print_b(a1)
c64scr.print(" * ") txt.print(" * ")
c64scr.print_b(a2) txt.print_b(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_b(r) txt.print_b(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub mul_uword(uword a1, uword a2, uword c) { sub mul_uword(uword a1, uword a2, uword c) {
uword r = a1*a2 uword r = a1*a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("uword ") txt.print("uword ")
c64scr.print_uw(a1) txt.print_uw(a1)
c64scr.print(" * ") txt.print(" * ")
c64scr.print_uw(a2) txt.print_uw(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_uw(r) txt.print_uw(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub mul_word(word a1, word a2, word c) { sub mul_word(word a1, word a2, word c) {
word r = a1*a2 word r = a1*a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("word ") txt.print("word ")
c64scr.print_w(a1) txt.print_w(a1)
c64scr.print(" * ") txt.print(" * ")
c64scr.print_w(a2) txt.print_w(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_w(r) txt.print_w(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub mul_float(float a1, float a2, float c) { sub mul_float(float a1, float a2, float c) {
float r = a1*a2 float r = a1*a2
if abs(r-c)<0.00001 if abs(r-c)<0.00001
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("float ") txt.print("float ")
c64flt.print_f(a1) floats.print_f(a1)
c64scr.print(" * ") txt.print(" * ")
c64flt.print_f(a2) floats.print_f(a2)
c64scr.print(" = ") txt.print(" = ")
c64flt.print_f(r) floats.print_f(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
} }

View File

@ -1,6 +1,5 @@
%import c64lib %import floats
%import c64utils %import textio
%import c64flt
%zeropage basicsafe %zeropage basicsafe
main { main {
@ -30,92 +29,81 @@ main {
plus_float(1.5,2.5,4.0) plus_float(1.5,2.5,4.0)
plus_float(-1.5,3.5,2.0) plus_float(-1.5,3.5,2.0)
plus_float(-1.1,3.3,2.2) plus_float(-1.1,3.3,2.2)
check_eval_stack()
} }
sub plus_ubyte(ubyte a1, ubyte a2, ubyte c) { sub plus_ubyte(ubyte a1, ubyte a2, ubyte c) {
ubyte r = a1+a2 ubyte r = a1+a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("ubyte ") txt.print("ubyte ")
c64scr.print_ub(a1) txt.print_ub(a1)
c64scr.print(" + ") txt.print(" + ")
c64scr.print_ub(a2) txt.print_ub(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_ub(r) txt.print_ub(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub plus_byte(byte a1, byte a2, byte c) { sub plus_byte(byte a1, byte a2, byte c) {
byte r = a1+a2 byte r = a1+a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("byte ") txt.print("byte ")
c64scr.print_b(a1) txt.print_b(a1)
c64scr.print(" + ") txt.print(" + ")
c64scr.print_b(a2) txt.print_b(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_b(r) txt.print_b(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub plus_uword(uword a1, uword a2, uword c) { sub plus_uword(uword a1, uword a2, uword c) {
uword r = a1+a2 uword r = a1+a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("uword ") txt.print("uword ")
c64scr.print_uw(a1) txt.print_uw(a1)
c64scr.print(" + ") txt.print(" + ")
c64scr.print_uw(a2) txt.print_uw(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_uw(r) txt.print_uw(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub plus_word(word a1, word a2, word c) { sub plus_word(word a1, word a2, word c) {
word r = a1+a2 word r = a1+a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("word ") txt.print("word ")
c64scr.print_w(a1) txt.print_w(a1)
c64scr.print(" + ") txt.print(" + ")
c64scr.print_w(a2) txt.print_w(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_w(r) txt.print_w(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub plus_float(float a1, float a2, float c) { sub plus_float(float a1, float a2, float c) {
float r = a1+a2 float r = a1+a2
if abs(r-c)<0.00001 if abs(r-c)<0.00001
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("float ") txt.print("float ")
c64flt.print_f(a1) floats.print_f(a1)
c64scr.print(" + ") txt.print(" + ")
c64flt.print_f(a2) floats.print_f(a2)
c64scr.print(" = ") txt.print(" = ")
c64flt.print_f(r) floats.print_f(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
} }

View File

@ -1,14 +1,14 @@
%import c64utils %import floats
%import c64flt %import textio
%option enable_floats
%zeropage basicsafe %zeropage basicsafe
main { main {
sub start() { sub start() {
c64scr.plot(0,24) txt.plot(0,24)
ubyte Y
ubyte ub=200 ubyte ub=200
byte bb=-100 byte bb=-100
uword uw = 2000 uword uw = 2000
@ -20,7 +20,7 @@ main {
word[3] warr = -1000 word[3] warr = -1000
float[3] flarr = 999.99 float[3] flarr = 999.99
c64scr.print("++\n") txt.print("++\n")
ub++ ub++
bb++ bb++
uw++ uw++
@ -51,7 +51,7 @@ main {
check_uw(uwarr[1], 2001) check_uw(uwarr[1], 2001)
check_w(warr[1], -999) check_w(warr[1], -999)
c64scr.print("--\n") txt.print("--\n")
ub-- ub--
bb-- bb--
uw-- uw--
@ -76,76 +76,65 @@ main {
check_b(barr[1], -100) check_b(barr[1], -100)
check_uw(uwarr[1], 2000) check_uw(uwarr[1], 2000)
check_w(warr[1], -1000) check_w(warr[1], -1000)
check_eval_stack()
} }
sub check_ub(ubyte value, ubyte expected) { sub check_ub(ubyte value, ubyte expected) {
if value==expected if value==expected
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print(" ubyte ") txt.print(" ubyte ")
c64scr.print_ub(value) txt.print_ub(value)
c64.CHROUT(',') c64.CHROUT(',')
c64scr.print_ub(expected) txt.print_ub(expected)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub check_b(byte value, byte expected) { sub check_b(byte value, byte expected) {
if value==expected if value==expected
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print(" byte ") txt.print(" byte ")
c64scr.print_b(value) txt.print_b(value)
c64.CHROUT(',') c64.CHROUT(',')
c64scr.print_b(expected) txt.print_b(expected)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub check_uw(uword value, uword expected) { sub check_uw(uword value, uword expected) {
if value==expected if value==expected
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print(" uword ") txt.print(" uword ")
c64scr.print_uw(value) txt.print_uw(value)
c64.CHROUT(',') c64.CHROUT(',')
c64scr.print_uw(expected) txt.print_uw(expected)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub check_w(word value, word expected) { sub check_w(word value, word expected) {
if value==expected if value==expected
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print(" word ") txt.print(" word ")
c64scr.print_w(value) txt.print_w(value)
c64.CHROUT(',') c64.CHROUT(',')
c64scr.print_w(expected) txt.print_w(expected)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub check_fl(float value, float expected) { sub check_fl(float value, float expected) {
if value==expected if value==expected
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print(" float ") txt.print(" float ")
c64flt.print_f(value) floats.print_f(value)
c64.CHROUT(',') c64.CHROUT(',')
c64flt.print_f(expected) floats.print_f(expected)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
} }

View File

@ -1,6 +1,4 @@
%import c64lib %import textio
%import c64utils
%import c64flt
%zeropage basicsafe %zeropage basicsafe
main { main {
@ -15,45 +13,35 @@ main {
remainder_uword(40000,511,142) remainder_uword(40000,511,142)
remainder_uword(40000,500,0) remainder_uword(40000,500,0)
remainder_uword(43211,12,11) remainder_uword(43211,12,11)
check_eval_stack()
} }
sub remainder_ubyte(ubyte a1, ubyte a2, ubyte c) { sub remainder_ubyte(ubyte a1, ubyte a2, ubyte c) {
ubyte r = a1%a2 ubyte r = a1%a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("ubyte ") txt.print("ubyte ")
c64scr.print_ub(a1) txt.print_ub(a1)
c64scr.print(" % ") txt.print(" % ")
c64scr.print_ub(a2) txt.print_ub(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_ub(r) txt.print_ub(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub remainder_uword(uword a1, uword a2, uword c) { sub remainder_uword(uword a1, uword a2, uword c) {
uword r = a1%a2 uword r = a1%a2
if r==c if r==c
c64scr.print(" ok ") txt.print(" ok ")
else else
c64scr.print("err! ") txt.print("err! ")
c64scr.print("uword ") txt.print("uword ")
c64scr.print_uw(a1) txt.print_uw(a1)
c64scr.print(" % ") txt.print(" % ")
c64scr.print_uw(a2) txt.print_uw(a2)
c64scr.print(" = ") txt.print(" = ")
c64scr.print_uw(r) txt.print_uw(r)
c64.CHROUT('\n') c64.CHROUT('\n')
} }
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
} }

View File

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

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 %target c64
%import c64utils %import syslib
%import textio
%zeropage basicsafe %zeropage basicsafe
main { main {
@ -15,13 +16,13 @@ main {
c64.SCROLX &= %11110111 ; 38 column mode c64.SCROLX &= %11110111 ; 38 column mode
c64utils.set_rasterirq(1) ; enable animation c64.set_rasterirq(1) ; enable animation
ubyte target_height = 10 ubyte target_height = 10
ubyte active_height = 24 ubyte active_height = 24
ubyte upwards = true ubyte upwards = true
forever { repeat {
ubyte mountain = 223 ; slope upwards ubyte mountain = 223 ; slope upwards
if active_height < target_height { if active_height < target_height {
active_height++ active_height++
@ -43,25 +44,27 @@ main {
} }
perform_scroll = false perform_scroll = false
c64scr.scroll_left_full(true) txt.scroll_left(true)
if c64.RASTER & 1
; float the balloon
if rnd() & %10000
c64.SPXY[1] ++ c64.SPXY[1] ++
else else
c64.SPXY[1] -- c64.SPXY[1] --
ubyte yy ubyte yy
for yy in 0 to active_height-1 { for yy in 0 to active_height-1 {
c64scr.setcc(39, yy, 32, 2) ; clear top of screen txt.setcc(39, yy, 32, 2) ; clear top of screen
} }
c64scr.setcc(39, active_height, mountain, 8) ; mountain edge txt.setcc(39, active_height, mountain, 8) ; mountain edge
for yy in active_height+1 to 24 { for yy in active_height+1 to 24 {
c64scr.setcc(39, yy, 160, 8) ; draw mountain txt.setcc(39, yy, 160, 8) ; draw mountain
} }
yy = rnd() yy = rnd()
if yy > 100 { if yy > 100 {
; draw a star ; draw a star
c64scr.setcc(39, yy % (active_height-1), '.', rnd()) txt.setcc(39, yy % (active_height-1), '.', rnd())
} }
if yy > 200 { if yy > 200 {
@ -74,12 +77,12 @@ main {
tree = 65 tree = 65
if rnd() > 130 if rnd() > 130
treecolor = 13 treecolor = 13
c64scr.setcc(39, active_height, tree, treecolor) txt.setcc(39, active_height, tree, treecolor)
} }
if yy > 235 { if yy > 235 {
; draw a camel ; draw a camel
c64scr.setcc(39, active_height, 94, 9) txt.setcc(39, active_height, 94, 9)
} }
} }
} }

View File

@ -1,11 +1,13 @@
%target c64
%import syslib
%import textio
%zeropage basicsafe %zeropage basicsafe
%import c64lib
main { main {
sub start() { sub start() {
c64scr.print("playing the music from boulderdash,\nmade in 1984 by peter liepa.\n\n") txt.print("playing the music from boulderdash,\nmade in 1984 by peter liepa.\n\n")
c64utils.set_rasterirq(60) ; enable raster irq c64.set_rasterirq(60) ; enable raster irq
} }
} }

View File

@ -1,4 +1,6 @@
%import c64lib %target c64
%import textio
%import syslib
main { main {
@ -12,11 +14,11 @@ sub start() {
c64.SR2 = %00000000 c64.SR2 = %00000000
c64.MVOL = 15 c64.MVOL = 15
c64scr.print("will play the music from boulderdash,\nmade in 1984 by peter liepa.\npress enter to start: ") txt.print("will play the music from boulderdash,\nmade in 1984 by peter liepa.\npress enter to start: ")
void c64.CHRIN() void c64.CHRIN()
c64.CLEARSCR() c64.CLEARSCR()
forever { repeat {
uword note uword note
for note in notes { for note in notes {
ubyte note1 = lsb(note) ubyte note1 = lsb(note)
@ -37,21 +39,20 @@ sub start() {
} }
sub delay() { sub delay() {
ubyte d repeat 8 {
for d in 0 to 12 { ubyte jiffy = c64.TIME_LO
while c64.RASTER!=0 { while c64.TIME_LO==jiffy {
; tempo delay synced to screen refresh
} }
} }
} }
sub print_notes(ubyte n1, ubyte n2) { sub print_notes(ubyte n1, ubyte n2) {
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.plot(n1/2, 24) txt.plot(n1/2, 24)
c64.COLOR=7 txt.color(7)
c64.CHROUT('Q') c64.CHROUT('Q')
c64scr.plot(n2/2, 24) txt.plot(n2/2, 24)
c64.COLOR=4 txt.color(4)
c64.CHROUT('Q') c64.CHROUT('Q')
} }

View File

@ -1,252 +0,0 @@
%import c64lib
; bitmap pixel graphics module for the C64
; only black/white monchrome for now
; you could put this code at $4000 which is after the bitmap screen in memory ($2000-$3fff),
; this leaves more space for user program code.
graphics {
const uword bitmap_address = $2000
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
c64.SCROLY |= %00100000
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
memset(bitmap_address, 320*200/8, 0)
c64scr.clear_screen($10, 0) ; pixel color $1 (white) backround $0 (black)
}
sub line(uword x1, ubyte y1, uword x2, ubyte y2) {
; Bresenham algorithm.
; This code special cases various quadrant loops to allow simple ++ and -- operations.
if y1>y2 {
; make sure dy is always positive to avoid 8 instead of just 4 special cases
swap(x1, x2)
swap(y1, y2)
}
word d = 0
ubyte positive_ix = true
word dx = x2 - x1 as word
word dy = y2 as word - y1 as word
if dx < 0 {
dx = -dx
positive_ix = false
}
dx *= 2
dy *= 2
plotx = x1
if dx >= dy {
if positive_ix {
forever {
plot(y1)
if plotx==x2
return
plotx++
d += dy
if d > dx {
y1++
d -= dx
}
}
} else {
forever {
plot(y1)
if plotx==x2
return
plotx--
d += dy
if d > dx {
y1++
d -= dx
}
}
}
}
else {
if positive_ix {
forever {
plot(y1)
if y1 == y2
return
y1++
d += dx
if d > dy {
plotx++
d -= dy
}
}
} else {
forever {
plot(y1)
if y1 == y2
return
y1++
d += dx
if d > dy {
plotx--
d -= dy
}
}
}
}
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm
ubyte ploty
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
while xx>=yy {
plotx = xcenter + xx
ploty = ycenter + yy
plot(ploty)
plotx = xcenter - xx
plot(ploty)
plotx = xcenter + xx
ploty = ycenter - yy
plot(ploty)
plotx = xcenter - xx
plot(ploty)
plotx = xcenter + yy
ploty = ycenter + xx
plot(ploty)
plotx = xcenter - yy
plot(ploty)
plotx = xcenter + yy
ploty = ycenter - xx
plot(ploty)
plotx = xcenter - yy
plot(ploty)
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
}
}
}
sub disc(uword cx, ubyte cy, ubyte radius) {
; Midpoint algorithm, filled
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
while xx>=yy {
ubyte cy_plus_yy = cy + yy
ubyte cy_min_yy = cy - yy
ubyte cy_plus_xx = cy + xx
ubyte cy_min_xx = cy - xx
for plotx in cx to cx+xx {
plot(cy_plus_yy)
plot(cy_min_yy)
}
for plotx in cx-xx to cx-1 {
plot(cy_plus_yy)
plot(cy_min_yy)
}
for plotx in cx to cx+yy {
plot(cy_plus_xx)
plot(cy_min_xx)
}
for plotx in cx-yy to cx {
plot(cy_plus_xx)
plot(cy_min_xx)
}
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
}
}
}
; here is the non-asm code for the plot routine below:
; sub plot_nonasm(uword px, ubyte py) {
; ubyte[] ormask = [128, 64, 32, 16, 8, 4, 2, 1]
; uword addr = bitmap_address + 320*(py>>3) + (py & 7) + (px & %0000000111111000)
; @(addr) |= ormask[lsb(px) & 7]
; }
uword plotx ; 0..319 ; separate 'parameter' for plot()
asmsub plot(ubyte ploty @A) { ; plotx is 16 bits 0 to 319... doesn't fit in a register
%asm {{
tay
stx c64.SCRATCH_ZPREGX
lda plotx+1
sta c64.SCRATCH_ZPWORD2+1
lsr a ; 0
sta c64.SCRATCH_ZPWORD2
lda plotx
pha
and #7
tax
lda _y_lookup_lo,y
clc
adc c64.SCRATCH_ZPWORD2
sta c64.SCRATCH_ZPWORD2
lda _y_lookup_hi,y
adc c64.SCRATCH_ZPWORD2+1
sta c64.SCRATCH_ZPWORD2+1
pla ; plotx
and #%11111000
tay
lda (c64.SCRATCH_ZPWORD2),y
ora _ormask,x
sta (c64.SCRATCH_ZPWORD2),y
ldx c64.SCRATCH_ZPREGX
rts
_ormask .byte 128, 64, 32, 16, 8, 4, 2, 1
; note: this can be even faster if we also have a 256 byte x-lookup table, but hey.
; see http://codebase64.org/doku.php?id=base:various_techniques_to_calculate_adresses_fast_common_screen_formats_for_pixel_graphics
; the y lookup tables encodes this formula: bitmap_address + 320*(py>>3) + (py & 7) (y from 0..199)
_y_lookup_hi
.byte $20, $20, $20, $20, $20, $20, $20, $20, $21, $21, $21, $21, $21, $21, $21, $21
.byte $22, $22, $22, $22, $22, $22, $22, $22, $23, $23, $23, $23, $23, $23, $23, $23
.byte $25, $25, $25, $25, $25, $25, $25, $25, $26, $26, $26, $26, $26, $26, $26, $26
.byte $27, $27, $27, $27, $27, $27, $27, $27, $28, $28, $28, $28, $28, $28, $28, $28
.byte $2a, $2a, $2a, $2a, $2a, $2a, $2a, $2a, $2b, $2b, $2b, $2b, $2b, $2b, $2b, $2b
.byte $2c, $2c, $2c, $2c, $2c, $2c, $2c, $2c, $2d, $2d, $2d, $2d, $2d, $2d, $2d, $2d
.byte $2f, $2f, $2f, $2f, $2f, $2f, $2f, $2f, $30, $30, $30, $30, $30, $30, $30, $30
.byte $31, $31, $31, $31, $31, $31, $31, $31, $32, $32, $32, $32, $32, $32, $32, $32
.byte $34, $34, $34, $34, $34, $34, $34, $34, $35, $35, $35, $35, $35, $35, $35, $35
.byte $36, $36, $36, $36, $36, $36, $36, $36, $37, $37, $37, $37, $37, $37, $37, $37
.byte $39, $39, $39, $39, $39, $39, $39, $39, $3a, $3a, $3a, $3a, $3a, $3a, $3a, $3a
.byte $3b, $3b, $3b, $3b, $3b, $3b, $3b, $3b, $3c, $3c, $3c, $3c, $3c, $3c, $3c, $3c
.byte $3e, $3e, $3e, $3e, $3e, $3e, $3e, $3e
_y_lookup_lo
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07, $40, $41, $42, $43, $44, $45, $46, $47
.byte $80, $81, $82, $83, $84, $85, $86, $87, $c0, $c1, $c2, $c3, $c4, $c5, $c6, $c7
.byte $00, $01, $02, $03, $04, $05, $06, $07
}}
}
}

View File

@ -0,0 +1,110 @@
%import textio
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
main {
sub start() {
byte v1
byte v2
v1 = 100
v2 = 127
if v1==v2
txt.print("error in 100==127!\n")
else
txt.print("ok: 100 not == 127\n")
if v1!=v2
txt.print("ok: 100 != 127\n")
else
txt.print("error in 100!=127!\n")
if v1<v2
txt.print("ok: 100 < 127\n")
else
txt.print("error in 100<127!\n")
if v1<=v2
txt.print("ok: 100 <= 127\n")
else
txt.print("error in 100<=127!\n")
if v1>v2
txt.print("error in 100>127!\n")
else
txt.print("ok: 100 is not >127\n")
if v1>=v2
txt.print("error in 100>=127!\n")
else
txt.print("ok: 100 is not >=127\n")
v1 = 125
v2 = 22
if v1==v2
txt.print("error in 125==22!\n")
else
txt.print("ok: 125 not == 22\n")
if v1!=v2
txt.print("ok: 125 != 22\n")
else
txt.print("error in 125!=22!\n")
if v1<v2
txt.print("error in 125<22!\n")
else
txt.print("ok: 125 is not < 22\n")
if v1<=v2
txt.print("error in 125<=22!\n")
else
txt.print("ok: 125 is not <= 22\n")
if v1>v2
txt.print("ok: 125 > 22\n")
else
txt.print("error in 125>22!\n")
if v1>=v2
txt.print("ok: 125 >= 22\n")
else
txt.print("error in 125>=22!\n")
v1 = 22
v2 = 22
if v1==v2
txt.print("ok: 22 == 22\n")
else
txt.print("error in 22==22!\n")
if v1!=v2
txt.print("error in 22!=22!\n")
else
txt.print("ok: 22 is not != 22\n")
if v1<v2
txt.print("error in 22<22!\n")
else
txt.print("ok: 22 is not < 22\n")
if v1<=v2
txt.print("ok: 22 <= 22\n")
else
txt.print("error in 22<=22!\n")
if v1>v2
txt.print("error in 22>22!\n")
else
txt.print("ok: 22 is not > 22\n")
if v1>=v2
txt.print("ok: 22 >= 22\n")
else
txt.print("error in 22>=22!\n")
}
}

View File

@ -0,0 +1,111 @@
%import textio
%import floats
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
main {
sub start() {
float v1
float v2
v1 = 1.11
v2 = 699.99
if v1==v2
txt.print("error in 1.11==699.99!\n")
else
txt.print("ok: 1.11 not == 699.99\n")
if v1!=v2
txt.print("ok: 1.11 != 699.99\n")
else
txt.print("error in 1.11!=699.99!\n")
if v1<v2
txt.print("ok: 1.11 < 699.99\n")
else
txt.print("error in 1.11<699.99!\n")
if v1<=v2
txt.print("ok: 1.11 <= 699.99\n")
else
txt.print("error in 1.11<=699.99!\n")
if v1>v2
txt.print("error in 1.11>699.99!\n")
else
txt.print("ok: 1.11 is not >699.99\n")
if v1>=v2
txt.print("error in 1.11>=699.99!\n")
else
txt.print("ok: 1.11 is not >=699.99\n")
v1 = 555.5
v2 = -22.2
if v1==v2
txt.print("error in 555.5==-22.2!\n")
else
txt.print("ok: 555.5 not == -22.2\n")
if v1!=v2
txt.print("ok: 555.5 != -22.2\n")
else
txt.print("error in 555.5!=-22.2!\n")
if v1<v2
txt.print("error in 555.5<-22.2!\n")
else
txt.print("ok: 555.5 is not < -22.2\n")
if v1<=v2
txt.print("error in 555.5<=-22.2!\n")
else
txt.print("ok: 555.5 is not <= -22.2\n")
if v1>v2
txt.print("ok: 555.5 > -22.2\n")
else
txt.print("error in 555.5>-22.2!\n")
if v1>=v2
txt.print("ok: 555.5 >= -22.2\n")
else
txt.print("error in 555.5>=-22.2!\n")
v1 = -22.2
v2 = -22.2
if v1==v2
txt.print("ok: -22.2 == -22.2\n")
else
txt.print("error in -22.2==-22.2!\n")
if v1!=v2
txt.print("error in -22.2!=-22.2!\n")
else
txt.print("ok: -22.2 is not != -22.2\n")
if v1<v2
txt.print("error in -22.2<-22.2!\n")
else
txt.print("ok: -22.2 is not < -22.2\n")
if v1<=v2
txt.print("ok: -22.2 <= -22.2\n")
else
txt.print("error in -22.2<=-22.2!\n")
if v1>v2
txt.print("error in -22.2>-22.2!\n")
else
txt.print("ok: -22.2 is not > -22.2\n")
if v1>=v2
txt.print("ok: -22.2 >= -22.2\n")
else
txt.print("error in -22.2>=-22.2!\n")
}
}

View File

@ -0,0 +1,111 @@
%import textio
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
main {
sub start() {
ubyte v1
ubyte v2
v1 = 100
v2 = 200
if v1==v2
txt.print("error in 100==200!\n")
else
txt.print("ok: 100 not == 200\n")
if v1!=v2
txt.print("ok: 100 != 200\n")
else
txt.print("error in 100!=200!\n")
if v1<v2
txt.print("ok: 100 < 200\n")
else
txt.print("error in 100<200!\n")
if v1<=v2
txt.print("ok: 100 <= 200\n")
else
txt.print("error in 100<=200!\n")
if v1>v2
txt.print("error in 100>200!\n")
else
txt.print("ok: 100 is not >200\n")
if v1>=v2
txt.print("error in 100>=200!\n")
else
txt.print("ok: 100 is not >=200\n")
v1 = 155
v2 = 22
if v1==v2
txt.print("error in 155==22!\n")
else
txt.print("ok: 155 not == 22\n")
if v1!=v2
txt.print("ok: 155 != 22\n")
else
txt.print("error in 155!=22!\n")
if v1<v2
txt.print("error in 155<22!\n")
else
txt.print("ok: 155 is not < 22\n")
if v1<=v2
txt.print("error in 155<=22!\n")
else
txt.print("ok: 155 is not <= 22\n")
if v1>v2
txt.print("ok: 155 > 22\n")
else
txt.print("error in 155>22!\n")
if v1>=v2
txt.print("ok: 155 >= 22\n")
else
txt.print("error in 155>=22!\n")
v1 = 22
v2 = 22
if v1==v2
txt.print("ok: 22 == 22\n")
else
txt.print("error in 22==22!\n")
if v1!=v2
txt.print("error in 22!=22!\n")
else
txt.print("ok: 22 is not != 22\n")
if v1<v2
txt.print("error in 22<22!\n")
else
txt.print("ok: 22 is not < 22\n")
if v1<=v2
txt.print("ok: 22 <= 22\n")
else
txt.print("error in 22<=22!\n")
if v1>v2
txt.print("error in 22>22!\n")
else
txt.print("ok: 22 is not > 22\n")
if v1>=v2
txt.print("ok: 22 >= 22\n")
else
txt.print("error in 22>=22!\n")
}
}

View File

@ -0,0 +1,110 @@
%import textio
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
main {
sub start() {
uword v1
uword v2
v1 = 100
v2 = 64444
if v1==v2
txt.print("error in 100==64444!\n")
else
txt.print("ok: 100 not == 64444\n")
if v1!=v2
txt.print("ok: 100 != 64444\n")
else
txt.print("error in 100!=64444!\n")
if v1<v2
txt.print("ok: 100 < 64444\n")
else
txt.print("error in 100<64444!\n")
if v1<=v2
txt.print("ok: 100 <= 64444\n")
else
txt.print("error in 100<=64444!\n")
if v1>v2
txt.print("error in 100>64444!\n")
else
txt.print("ok: 100 is not >64444\n")
if v1>=v2
txt.print("error in 100>=64444!\n")
else
txt.print("ok: 100 is not >=64444\n")
v1 = 5555
v2 = 322
if v1==v2
txt.print("error in 5555==322!\n")
else
txt.print("ok: 5555 not == 322\n")
if v1!=v2
txt.print("ok: 5555 != 322\n")
else
txt.print("error in 5555!=322!\n")
if v1<v2
txt.print("error in 5555<322!\n")
else
txt.print("ok: 5555 is not < 322\n")
if v1<=v2
txt.print("error in 5555<=322!\n")
else
txt.print("ok: 5555 is not <= 322\n")
if v1>v2
txt.print("ok: 5555 > 322\n")
else
txt.print("error in 5555>322!\n")
if v1>=v2
txt.print("ok: 5555 >= 322\n")
else
txt.print("error in 5555>=322!\n")
v1 = 322
v2 = 322
if v1==v2
txt.print("ok: 322 == 322\n")
else
txt.print("error in 322==322!\n")
if v1!=v2
txt.print("error in 322!=322!\n")
else
txt.print("ok: 322 is not != 322\n")
if v1<v2
txt.print("error in 322<322!\n")
else
txt.print("ok: 322 is not < 322\n")
if v1<=v2
txt.print("ok: 322 <= 322\n")
else
txt.print("error in 322<=322!\n")
if v1>v2
txt.print("error in 322>322!\n")
else
txt.print("ok: 322 is not > 322\n")
if v1>=v2
txt.print("ok: 322 >= 322\n")
else
txt.print("error in 322>=322!\n")
}
}

View File

@ -0,0 +1,142 @@
%import textio
%zeropage basicsafe
; Note: this program is compatible with C64 and CX16.
main {
sub start() {
word v1
word v2
v1 = 100
v2 = 30333
if v1==v2
txt.print("error in 100==30333!\n")
else
txt.print("ok: 100 not == 30333\n")
if v1!=v2
txt.print("ok: 100 != 30333\n")
else
txt.print("error in 100!=30333!\n")
if v1<v2
txt.print("ok: 100 < 30333\n")
else
txt.print("error in 100<30333!\n")
if v1<=v2
txt.print("ok: 100 <= 30333\n")
else
txt.print("error in 100<=30333!\n")
if v1>v2
txt.print("error in 100>30333!\n")
else
txt.print("ok: 100 is not >30333\n")
if v1>=v2
txt.print("error in 100>=30333!\n")
else
txt.print("ok: 100 is not >=30333\n")
v1 = 125
v2 = -222
if v1==v2
txt.print("error in 125==-222!\n")
else
txt.print("ok: 125 not == -222\n")
if v1!=v2
txt.print("ok: 125 != -222\n")
else
txt.print("error in 125!=-222!\n")
if v1<v2
txt.print("error in 125<-222!\n")
else
txt.print("ok: 125 is not < -222\n")
if v1<=v2
txt.print("error in 125<=-222!\n")
else
txt.print("ok: 125 is not <= -222\n")
if v1>v2
txt.print("ok: 125 > -222\n")
else
txt.print("error in 125>-222!\n")
if v1>=v2
txt.print("ok: 125 >= -222\n")
else
txt.print("error in 125>=-222!\n")
v1 = -222
v2 = -222
if v1==v2
txt.print("ok: -222 == -222\n")
else
txt.print("error in -222==-222!\n")
if v1!=v2
txt.print("error in -222!=-222!\n")
else
txt.print("ok: -222 is not != -222\n")
if v1<v2
txt.print("error in -222<-222!\n")
else
txt.print("ok: -222 is not < -222\n")
if v1<=v2
txt.print("ok: -222 <= -222\n")
else
txt.print("error in -222<=-222!\n")
if v1>v2
txt.print("error in -222>-222!\n")
else
txt.print("ok: -222 is not > -222\n")
if v1>=v2
txt.print("ok: -222 >= -222\n")
else
txt.print("error in -222>=-222!\n")
v1 = 1000
v2 = 1000
if v1==v2
txt.print("ok: 1000 == 1000\n")
else
txt.print("error in 1000==1000!\n")
if v1!=v2
txt.print("error in 1000!=1000!\n")
else
txt.print("ok: 1000 is not != 1000\n")
if v1<v2
txt.print("error in 1000<1000!\n")
else
txt.print("ok: 1000 is not < 1000\n")
if v1<=v2
txt.print("ok: 1000 <= 1000\n")
else
txt.print("error in 1000<=1000!\n")
if v1>v2
txt.print("error in 1000>1000!\n")
else
txt.print("ok: 1000 is not > 1000\n")
if v1>=v2
txt.print("ok: 1000 >= 1000\n")
else
txt.print("error in 1000>=1000!\n")
}
}

1008
examples/cmp/comparisons.p8 Normal file

File diff suppressed because it is too large Load Diff

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