Compare commits

...

684 Commits
v1.62 ... v5.2

Author SHA1 Message Date
895b30f7e5 version 5.2 2020-12-01 22:49:08 +01:00
b985604e22 slight tweak to word bitshift for large shift values 2020-12-01 22:48:02 +01:00
f7953e4ef3 fix float comparison error that creeped in with no longer using the stack for that 2020-12-01 22:19:03 +01:00
63483d1f0e warnings, errors and todos 2020-12-01 03:24:06 +01:00
8b981f03bf optimized reg_lesseq_w (word <= word) to avoid using extra zp word, by swapping operands 2020-12-01 02:09:48 +01:00
d0d0910bf2 corrected greatereq_w (word >= word) 2020-12-01 01:57:12 +01:00
57ac820767 readme 2020-11-30 22:42:51 +01:00
b8bda867b6 optimized reg_lesseq_w (word <= word) 2020-11-30 02:26:00 +01:00
05d3a2450c optimized reg_less_w (word < word) 2020-11-30 01:53:44 +01:00
d40788adfa optimized in-place array element modification to use simpler assignment asm code 2020-11-28 00:44:38 +01:00
83fbf86b1c no longer generate double assignment to the indexer var for in-place modifying array variable 2020-11-27 23:46:01 +01:00
e876008427 tiny tweak of typecasting str to uword 2020-11-26 19:21:07 +01:00
2b43353eb4 readme 2020-11-26 02:04:01 +01:00
a74403c347 float typecasts optimization 2020-11-26 01:52:48 +01:00
2f4c6c8697 float typecasts optimization 2020-11-26 01:39:27 +01:00
238d8197f5 byte/word typecasts optimized even further to just use cpu registers (and fixed sign extending AY) 2020-11-26 01:33:45 +01:00
53a600d87b fix typecasting of signed byte to signed word in a variable 2020-11-25 22:33:41 +01:00
2a0ffaf45d started to optimize typecasts to use translateExpression() less 2020-11-25 00:17:42 +01:00
936b046ed9 optimize word [operator] byte, without translateExpression() 2020-11-24 23:41:10 +01:00
378dcfe351 fix computation error of word - byte 2020-11-24 22:23:16 +01:00
0a330b9288 warmings 2020-11-24 22:21:54 +01:00
a88b40d6c1 fix stack corruption with bitshifts 2020-11-24 21:58:14 +01:00
09f25ffbd9 optimized in-place memory var modification, not using translateExpression() 2020-11-24 21:41:44 +01:00
ab1232d742 optimized in-place float var modification, not using translateExpression() 2020-11-24 01:09:24 +01:00
a7f56fe0fc remaining float comparisons with expression now without translateExpression() 2020-11-24 00:35:30 +01:00
58a9452c36 fixed the YSCROLL graphics mode on the C64 (mistake in 5.1) 2020-11-23 23:05:51 +01:00
6d8c4f403f updated Kotlin version to 1.4.20, updated targeted JDK version to 11 (LTS) 2020-11-23 22:28:24 +01:00
88b80fed90 returning float values now via fac1 instead of stack 2020-11-23 22:14:45 +01:00
acdbd0c391 todos for next version 2020-11-22 19:18:57 +01:00
d9a8cfed8c updated the compiled examples disk 2020-11-22 18:45:40 +01:00
122796fbba version 5.1 2020-11-22 18:36:04 +01:00
510ca042c9 stack tested for most example programs 2020-11-22 18:35:43 +01:00
125f6205f2 optimizing assigning an array value to a var 2020-11-22 17:44:55 +01:00
8136f3df5c float-const comparison optimizations 2020-11-22 16:54:02 +01:00
38d06a7e94 optimized float var comparison without translateExpression() 2020-11-22 15:05:45 +01:00
49db10539a optimized float var equality comparison without translateExpression() 2020-11-22 14:33:03 +01:00
8efe4c6267 Fixed compiler watch to work with multiple compilation modules 2020-11-22 13:11:33 +01:00
04d8bb8fbf Fixed compiler watch flag crashing when not used on a subdirectory. Fixes #20 2020-11-22 12:07:14 +01:00
08aa13c90c rnd() functions marked as having (internal) side effect 2020-11-22 02:09:32 +01:00
d1febc0208 all in-place byte assignments now without translateExpression() 2020-11-22 01:38:53 +01:00
5980e58ac6 word comparison jumps now without translateExpression() 2020-11-22 01:15:05 +01:00
e1dc283d4b byte comparison jumps now without translateExpression() 2020-11-21 23:31:26 +01:00
8be234973c rollback failed optimization of memory expressions (code size got too large) 2020-11-21 19:09:02 +01:00
7def8ff2cd beginning to optimize comparisons more 2020-11-21 18:44:17 +01:00
340b1c2e42 added balls demo/benchmark 2020-11-21 18:03:57 +01:00
7e0f7ba438 todos 2020-11-20 23:46:14 +01:00
fefd9b52a8 fix for loop with signed byte loopvar over non-const 2020-11-20 22:54:24 +01:00
afd155ac4f optimize for loops over non const range, without translateExpression() 2020-11-20 22:44:16 +01:00
ee724eb4f1 float variable casts without translateExpression() 2020-11-19 22:58:38 +01:00
2f1f20ea11 rename 2020-11-19 00:28:49 +01:00
063bcf17d8 various inplace modification for word vars now without translateExpression() 2020-11-19 00:08:10 +01:00
72509eef44 inplace modification for memory now without translateExpression() 2020-11-18 23:23:06 +01:00
2da28864e9 inplace not and invert for memory now without translateExpression() 2020-11-18 23:13:07 +01:00
4278f64682 fixed invalid value push for memreads with expression 2020-11-18 22:45:04 +01:00
59ae3c3fcd << and >> for byte values slightly optimized, no longer use translateExpression(). preparing for more operator optimizations. 2020-11-18 01:27:02 +01:00
7fa21fbdff @(...) in an expression is now more efficient, without translateExpression() 2020-11-18 00:58:04 +01:00
e95af7498e comparing function call result to 0 now more efficient, without translateExpression() 2020-11-18 00:05:48 +01:00
79c75adac1 repeat and when without translateExpression() 2020-11-17 23:52:13 +01:00
d212f69d89 ++/-- and @Pc without translateExpression() 2020-11-17 23:40:42 +01:00
edf5e69d39 optimized swap() 2020-11-15 18:04:54 +01:00
574eb0d174 refactoring asmassignment code blocks into utility functions 2020-11-15 17:44:47 +01:00
8bd4914e2f fix stack error for float casts 2020-11-15 17:34:27 +01:00
5ebaaff64b refactoring asmassignment code blocks into utility functions 2020-11-15 15:07:55 +01:00
5c9e0c9f51 emit extra nop for breakpoints so vice label list works again (requires 64tass 1.55.2257 or newer!) 2020-11-15 14:31:06 +01:00
8132edbb08 updated some compiled example 2020-11-10 22:51:01 +01:00
d29ce78c86 todos and version 2020-11-10 22:44:48 +01:00
94bc9d7a69 string compare in expression no longer via stack args 2020-11-10 21:48:28 +01:00
e8faec0932 re-introduced more aggressive binexpr splitting optimization 2020-11-10 21:17:33 +01:00
69ca4fe304 cleanup 2020-11-10 21:02:12 +01:00
cd99fe46fd finished call convention change for builtin functions now no longer via stack 2020-11-10 00:43:45 +01:00
4825b4dc68 fix passing address of pass-by-reference assignment to a UWORD 2020-11-10 00:35:24 +01:00
8d0607ef58 fix missing float casts 2020-11-09 23:57:50 +01:00
225295a7d8 fix float casts 2020-11-09 01:18:22 +01:00
4cd74daf53 float eval result var added, but some examples are broken 2020-11-08 18:54:02 +01:00
6eb9118197 example 2020-11-07 01:08:56 +01:00
d0bd2f522c rol and ror 2020-11-07 00:56:54 +01:00
661c757236 fix string compare in expressions 2020-11-06 22:59:56 +01:00
aaa20093ef cleaning up and correcting cc for builtin functions 2020-11-06 00:56:26 +01:00
1eecdd6fa3 fix error when taking address of struct var 2020-11-05 02:39:04 +01:00
800b5b2a43 cleaning up and correcting cc for builtin functions 2020-11-05 02:29:33 +01:00
9d17421c66 implemented the arithmetic functions with new cc. fixed sgn(). 2020-11-04 02:27:29 +01:00
0edd50e956 implemented cc for abs() 2020-11-03 23:01:23 +01:00
288d4f08b3 implemented cc for integer sin and cos variants 2020-11-03 22:42:59 +01:00
526e4b8bdc fix faulty binexpr splitting 2020-11-03 21:31:08 +01:00
e0c5ccc16b begun with converting builtin functions to new call convention 2020-11-02 23:00:20 +01:00
ebc2c614d7 use non-stack call conv for builtin functions 2020-11-02 19:59:27 +01:00
29f5a85158 callconv 2020-11-01 19:25:23 +01:00
8af2380a47 pair 2020-11-01 18:00:20 +01:00
431f2a2088 optimized memset and memcopy on CX16, memcopy can deal with any size now 2020-11-01 08:00:32 +01:00
e05ea887f6 implement proper returning of float values via FAC1 2020-11-01 06:27:17 +01:00
95c0425151 improved sqrt16 2020-11-01 05:45:49 +01:00
47cbc7b1f9 added a custom-charset example for the c64 2020-10-31 02:26:59 +01:00
e7b75d591c assigning float results from functions (from FAC1) 2020-10-31 01:22:19 +01:00
99f7d469f4 assigning string result from subroutine 2020-10-30 22:22:06 +01:00
8a6ef17fbf option 2020-10-30 21:51:15 +01:00
5f337a0bd9 fix typecheck of multiple returnvalues 2020-10-30 21:45:37 +01:00
87862f772a better handling of inferred type errors 2020-10-30 21:24:49 +01:00
3ab641aa21 removed @stack in subroutine args and returnvalues, can only use variables or registers now 2020-10-30 15:02:42 +01:00
3efa8da8e0 made versions of various builtin funcs returning value in registers 2020-10-30 14:35:20 +01:00
3e28ed4fe4 mader versions of abs() and sgn() returning value in register 2020-10-28 22:56:13 +01:00
44949460ed change for subroutine return values via registers instead of stack 2020-10-28 00:29:34 +01:00
83cc19ad6f preparing for subroutine return values via registers instead of stack 2020-10-23 20:56:10 +02:00
66bb98c479 fixed bugs in code assigning values from eval stack 2020-10-23 03:45:09 +02:00
ff3f985658 refactoring 2020-10-22 23:41:16 +02:00
2ba6c9ccbe textelite 1.1 finalize load/save, add it to examplesd disk 2020-10-20 21:49:06 +02:00
3eaf111e7d added 'slowwarn' cli option 2020-10-20 21:38:37 +02:00
30da26b9a9 tackling problem of invalid reuse of auto indexer var 2020-10-20 21:23:43 +02:00
e35ad0cc8f code cleanups 2020-10-20 17:54:16 +02:00
1a36302cf1 rest of optimizations following simplification of array indexer 2020-10-19 23:57:00 +02:00
82a28bb555 extra attempt to simplify add and subtract with negative numbers 2020-10-19 23:01:32 +02:00
c1ce0be451 slightly optimize expression code for most common cases +/- 1 , */div 2 2020-10-19 22:50:38 +02:00
c0a5f8fef0 removed double mul code 2020-10-19 21:32:44 +02:00
702cf304d0 implemented missing swap() operations 2020-10-19 21:26:11 +02:00
4dee8b6048 remove superfluous value eval 2020-10-19 02:38:26 +02:00
ec665e0cc1 fixed incorrect removal of certain assignments that are NOT double 2020-10-19 02:16:23 +02:00
aec3b82476 fixed bitshifting by more than the number of bits in the value 2020-10-19 02:05:01 +02:00
e83796b5b9 fixed bit shifting by 0. optimized bitshifting code. 2020-10-18 17:12:52 +02:00
8eb69d6eda vardecl with initializer expression are now optimized again (unless floats) 2020-10-18 16:15:05 +02:00
74b5124a42 removed restriction on array indexer expression again from docs and code... :) 2020-10-18 14:05:26 +02:00
b9706a180b fix array indexer bug 2020-10-18 13:49:53 +02:00
8aeb8a9bb7 reintroduce expressions for array indexing 2020-10-18 13:33:11 +02:00
8f2e166a22 annotated some high prio todos 2020-10-17 22:57:54 +02:00
fdd91170dc allow simple binary expressions as array indexing too, but not more 2020-10-17 22:43:35 +02:00
c40ddb061b example adjustments 2020-10-17 21:00:59 +02:00
353d6cfc55 doc about array index restriction 2020-10-17 20:35:36 +02:00
f37564c49c fixed 2020-10-17 19:59:48 +02:00
157484d94b adapted p8 code to restricted array indexing 2020-10-17 19:57:55 +02:00
7626c9fff7 only allow array indexing via a number, or a variable (eliminate complex expression calcs for array indexing, force explicit use of an index variable) 2020-10-17 19:57:55 +02:00
1f55f9fc49 removed 2 problematic ZP locations for the C64 2020-10-17 19:57:10 +02:00
2554bc7ef8 ordered the functions in the docs 2020-10-17 02:14:19 +02:00
7cb4100419 string can be compared directly (uses strcmp() automatically in asm) 2020-10-17 02:01:00 +02:00
2d3b7eb878 started making string compares use strcmp() automatically 2020-10-17 01:11:01 +02:00
4d01a78731 introduced strcmp() builtin function 2020-10-16 19:00:06 +02:00
a03e36828a fixed lines in assembly source optimizer 2020-10-16 01:48:03 +02:00
260fb65b06 making strcmp 2020-10-16 00:11:46 +02:00
9fb8526136 added conv.bin and hex string to number 2020-10-15 23:47:10 +02:00
26fc5ff5e2 preparing conv.bin and hex string to number 2020-10-15 23:10:28 +02:00
5060f0bb19 fixed assigning a memory byte from an array 2020-10-15 22:15:00 +02:00
beaf6d449b added short overview of the library modules 2020-10-15 21:30:03 +02:00
4d68b508a2 proper error if variable name is the same as its subroutine or block (that would create naming problems in the assembly code) 2020-10-15 20:48:18 +02:00
cd825e386d fix invalid address-of error when taking address of struct variable 2020-10-15 20:14:17 +02:00
095c8b2309 corrected name and added cx16logo library module for fun 2020-10-15 00:58:41 +02:00
8b6eb74c58 refactor 2020-10-14 23:43:38 +02:00
aba437e5a2 diskio load and save use kernel routines for load and save, and don't bother with SEQ files 2020-10-14 22:33:49 +02:00
efe3ed499b starting with load/save in textelite 2020-10-14 02:51:00 +02:00
5595564a1f todo strcmp 2020-10-14 01:22:43 +02:00
439761cb67 fixed C64 ZP addresses to allow disk I/O, introduced diskio library module 2020-10-14 01:17:18 +02:00
bee6c65293 fixed several bugs in the repeat assembly for loop sizes like 0 and 256 2020-10-13 21:48:15 +02:00
10145b946b invalid repeat loop code is generated... 2020-10-13 16:27:40 +02:00
ebf4b50059 reused existing CallGraph to check for recursion, which is now fixed. It's a warning too now. 2020-10-12 23:04:00 +02:00
07cce3b3fc version 4.5 2020-10-11 21:59:38 +02:00
f2c19afd95 version 4.5 2020-10-11 21:47:41 +02:00
d159e70e1c textelite travel commands 2020-10-11 21:38:25 +02:00
ac693a2541 textelite buy and sell commands 2020-10-11 19:29:18 +02:00
1e988116ce fixed precedence of comparison and bitwise operators 2020-10-11 19:02:53 +02:00
ec9e722927 added conv.str2byte and conv.str2ubyte 2020-10-11 18:36:20 +02:00
4cd5e8c378 textelite 2020-10-11 18:19:09 +02:00
b759d5e06a fixed X register corruption on Cx16 verions of float.GIVUAYFAY and GIVAYFAY 2020-10-11 17:46:19 +02:00
1469033c1e todo 2020-10-11 16:53:00 +02:00
c15fd75df7 asmassignment can now use arbitrary source symbols; optimized byte-word sign extesion with this to not use stack anymore 2020-10-11 15:44:08 +02:00
73524e01a6 really fix byte-word sign extension for function args as expression 2020-10-11 03:07:45 +02:00
9e54e11113 fixed string + string/ string * number 2020-10-11 02:34:04 +02:00
01ac5f29db fix byte-word sign extension for function args as expression 2020-10-11 01:38:34 +02:00
67a2241e32 textelite market start 2020-10-11 00:38:38 +02:00
72b6dc3de7 avoid crash when optimizer has multiple replacements of the same node 2020-10-11 00:37:35 +02:00
6f5b645995 textelite market start 2020-10-10 23:24:15 +02:00
458ad1de57 fix strlen on uword (pointer) instead of str 2020-10-10 23:24:05 +02:00
216f48b7c1 txtelite 2020-10-10 22:45:03 +02:00
b2d1757e5a asmgen: byte to word sign extensions 2020-10-10 15:39:48 +02:00
6e53eb9d5c asmgen: only generate storage byte for register saves in subroutine when it's actually needed 2020-10-10 15:02:56 +02:00
e5ee5be9c5 textelite 2020-10-10 04:42:17 +02:00
bd237b2b95 it's now possible in more places to assign arrays and put array literals without the need to define explicit variable. 2020-10-10 04:30:28 +02:00
d31cf766eb added missing doc picture 2020-10-10 02:51:02 +02:00
56d530ff04 txtelite with input loop 2020-10-10 01:46:19 +02:00
0bbb2240f2 txtelite with input loop 2020-10-10 01:35:46 +02:00
1c8e4dba73 added \' escape character 2020-10-10 01:28:57 +02:00
4a9956c4a4 txtelite species and planet naming fix 2020-10-10 01:15:26 +02:00
59c0e6ae32 added some more missing assignment codegens (word * byte etc) 2020-10-09 23:48:33 +02:00
94c30fc21e textelite 2020-10-09 22:47:42 +02:00
8bb3b3be20 fix repeat loop for variables when var == 0 2020-10-09 22:30:21 +02:00
85e3c2c5a2 textelite 2020-10-09 22:25:12 +02:00
4be381c597 fixed compiler optimizer crash because of conflicting expression replacements 2020-10-09 21:51:54 +02:00
6ff5470cf1 txtelite 2020-10-09 21:01:06 +02:00
151dcfdef9 code style 2020-10-08 21:47:07 +02:00
c282b4cb9f code style 2020-10-07 23:24:30 +02:00
c426f4626c added some more missing aug assign operator code 2020-10-07 22:53:18 +02:00
0e3c92626e fixed handling of main module when importing another. fixed diskdir closedown. 2020-10-07 21:55:00 +02:00
5099525e24 added missing register pair assignments. fixed compiler crashes 2020-10-07 03:43:02 +02:00
e22b4cbb67 fixed invalid errormessage about memory mapped strings 2020-10-07 01:35:39 +02:00
2b48828179 examples issues 2020-10-07 01:21:41 +02:00
3e181362dd optimized code for processing return values from asmsubs without intermediate estack. 2020-10-07 00:51:57 +02:00
71fd98e39e allow asmsub routines with multiple return values to be called (special case for return values in status register) 2020-10-07 00:33:42 +02:00
71cd8b6d51 cx16 cross-compile teaser screenshot 2020-10-05 19:59:51 +02:00
ad75fcbf7e txtelite 2020-10-05 19:49:13 +02:00
f8b04a6357 added status return flags to some kernel i/o operations 2020-10-05 19:48:21 +02:00
d8fcbb78d3 txtelite goatsoup 2020-10-04 21:53:16 +02:00
8408bf3789 another compiler crash fixed when dealing with functioncall returning a str 2020-10-04 21:53:08 +02:00
3e1185658e txtelite goatsoup 2020-10-04 21:35:37 +02:00
d778cdcd61 another compiler crash fixed when dealing with functioncall returning a str 2020-10-04 21:11:42 +02:00
90b303fc03 fix error message for invalid number of arguments 2020-10-04 19:28:22 +02:00
eb86b1270d txtelite 2020-10-04 19:23:36 +02:00
a1f0cc878b correct error message for faulty string variable declarations 2020-10-04 19:13:19 +02:00
f2e2720b15 compiler crash fixed when dealing with functioncall returning a str 2020-10-04 18:47:47 +02:00
ec8cfe1591 make string-assignment actually work (using strcpy) 2020-10-04 18:18:58 +02:00
22eac159e5 txtelite 2020-10-04 17:47:57 +02:00
956b0c3fa7 added \xHH escape character to strings, allow strings of length zero. 2020-10-04 13:05:43 +02:00
a6427e0949 added \$HH escape character to strings 2020-10-03 15:11:09 +02:00
22031f39b0 update compiled examples 2020-10-02 23:39:20 +02:00
c4673d3a67 v4.4 2020-10-02 23:32:45 +02:00
e83e021541 doc 2020-10-02 23:31:49 +02:00
c1f2ecd413 struct assignment from array value now checks number of elements 2020-10-02 22:48:39 +02:00
46fbe01df9 added codengeration for assigment of array of values to a struct variable (all members at once) 2020-10-02 22:37:52 +02:00
8647a8290e fix code generation for using struct vars in arrays and such 2020-10-02 22:21:18 +02:00
bac51f4b31 fix subtraction error for bytes 2020-10-02 21:30:32 +02:00
582aab180a oops 2020-10-02 02:39:19 +02:00
5fb714fcb2 expression splitter integrated into expression simplifier 2020-10-02 01:54:37 +02:00
3994de77d0 fix expression splitter handling related to code ballooning 2020-10-02 01:49:55 +02:00
24c8d1f1f4 expression splitter for vardecls with binexpr init expression 2020-10-02 00:34:12 +02:00
110f877dcc binexpr expression splitter for assignments 2020-10-02 00:04:21 +02:00
9cd3a9f8e8 fix isSameAs for ArrayIndexed expressions, and by extension, assignment.isAugmentable() 2020-10-01 23:26:43 +02:00
1464050bf5 expression splitter moved to separate optimizer 2020-10-01 02:58:12 +02:00
95e9e1b550 avoid adding unneeded variable initalization assignments. Improved removal of useless double assignments. 2020-10-01 00:39:49 +02:00
bda1c1c1eb reduce slow estack usage by splitting up simple binary expressions 2020-09-30 19:57:16 +02:00
d020a7974a reduce slow estack usage by splitting up simple binary expressions 2020-09-30 17:51:35 +02:00
a51fad3aab parentheses around binary exprs in source output 2020-09-30 16:38:54 +02:00
3cd32778bb don't split expressions referencing the target variable wrongly 2020-09-30 01:11:33 +02:00
8d67056f84 fixed estack corruption caused by c64 print_f 2020-09-29 21:12:16 +02:00
e986973b5e wrong floats 2020-09-29 04:05:45 +02:00
448c934cba optimized neg(x) and abs(x) 2020-09-29 03:58:17 +02:00
96ef7ba55d fixed ast to source for structs 2020-09-29 00:28:11 +02:00
4372de1e7e allow creating arrays of pointers to other arrays. Usefullness is very limited though... 2020-09-29 00:03:47 +02:00
af0fb88adf allow creating string arrays. Fixed array index scaling for word arrays. 2020-09-28 02:23:36 +02:00
066233eee8 todos 2020-09-27 22:05:44 +02:00
b6f85d10b0 reintroduced system reset at program exit if zeropage is clobbered 2020-09-27 22:00:36 +02:00
6f75413c09 some more optimizations in expressions with memreads 2020-09-27 21:43:40 +02:00
d45fe4ce74 fixed invalid eval stack ptr issue 2020-09-27 20:55:34 +02:00
e828c013e6 fix word+/-byte errors if byte was unsigned 2020-09-27 20:23:42 +02:00
988459f744 don't generate a byte storage for every single time a register needs saving 2020-09-27 16:26:02 +02:00
7c701bdf3f corrections 2020-09-27 14:14:45 +02:00
446fc35d5c avoid excessive comparisons for certain comparison expressions against zero 2020-09-27 03:55:59 +02:00
bec9cc7047 asm store/load same optimizer back.... 2020-09-27 02:45:59 +02:00
961380acb6 optimized float ==0 or 1 comparisons 2020-09-27 01:56:08 +02:00
84c0685a60 fix faulty comparison optimization 2020-09-27 01:40:12 +02:00
629222f103 larger 2020-09-26 19:59:57 +02:00
8c448e5bc2 finished optimized comparison asm generation 2020-09-26 19:55:04 +02:00
b5fa6c2d0a library modules imported from embedded resource now contain proper file path (useful for error messages) 2020-09-26 19:30:17 +02:00
680b2df08a just call the asmsub 2020-09-26 19:14:06 +02:00
09bd47f98b > 2020-09-26 19:02:29 +02:00
7f69f9ce4f <= 2020-09-26 18:04:43 +02:00
4179b4e543 all unsigned comparisons 2020-09-26 17:45:35 +02:00
66364554c4 new comparisons testprog 2020-09-26 16:11:47 +02:00
43f2448789 added (u)byte and (u)word '>' 2020-09-26 13:15:03 +02:00
130cee1e70 tweak '<' code 2020-09-26 12:47:40 +02:00
b976360248 fix fallthrough problem with 'when'. Fix too greedy asm optimization that caused conditional jumps to fail sometimes because the condition value wasn't loaded. 2020-09-26 00:22:55 +02:00
225bfc4164 fix 16+8 bit add and sub sign extensions 2020-09-25 22:51:59 +02:00
d7ceda4d82 removed the automatic system reset at program exit, this did't work with the new init code 2020-09-25 22:12:14 +02:00
14d091e60a crashes :( 2020-09-24 23:50:20 +02:00
2809668ef4 new asm code for (u)word and (u)byte < 2020-09-24 23:08:36 +02:00
bafb86e00b new asm code for (n)equals 2020-09-24 22:28:24 +02:00
f5db31b8ff do..until condition can now refer to variables defined in the loop's inner scope. 2020-09-24 19:26:07 +02:00
e1d0dbed0c do..until condition can now refer to variables defined in the loop's inner scope. 2020-09-23 23:24:32 +02:00
1d1fe364d0 added %option no_sysinit to avoid having the system re-initialization code executed at the start of the program 2020-09-23 23:01:47 +02:00
2b9316c4ff reworked program init logic so that it is included as the first thing inside main.start itself, to allow better stand alone asm 2020-09-23 22:29:21 +02:00
c50cbbb526 typo 2020-09-23 18:50:32 +02:00
b93d9ecd7e memtop cx16 2020-09-23 02:34:49 +02:00
96243db88b refresh compiled examples 2020-09-23 00:29:40 +02:00
4daf75a8cc better checks for invalid %output and %launcher values. Added diskdir examples. 2020-09-23 00:22:36 +02:00
8c63d7cf5b diskdir 2020-09-22 23:22:20 +02:00
6f78a32e64 diskdir 2020-09-22 23:12:43 +02:00
af6731c9c8 preparing version 4.3 2020-09-22 21:50:56 +02:00
25cf0d2b94 don't suggest a mult replacement routine to be used, faster ones are likely to require large tables 2020-09-22 21:19:01 +02:00
9389791d91 created own circle and disc subroutines for cx16 because its rom routine is not yet implemented and just does a BRK 2020-09-22 02:52:09 +02:00
aa8191d0a1 introduced graphics module wrapper for cx16 to make even more programs compatible 2020-09-22 02:21:16 +02:00
0d5c78e875 introduced graphics module wrapper for cx16 to make even more programs compatible 2020-09-22 02:12:01 +02:00
e8679ae03b fixed print_f on cx16. Some more examples are now multi-platform. 2020-09-22 01:45:51 +02:00
d1d224b7fc fixed print_f on cx16. Some more examples are now multi-platform. 2020-09-22 01:34:05 +02:00
df995f7bc9 fixed float zp problem on C64, added more zp locations to block list 2020-09-22 01:05:07 +02:00
af39502450 doc 2020-09-22 00:47:02 +02:00
ffa38955d6 improved scroll_down and scroll_up to use VERA dual data ports instead of a copybuffer 2020-09-22 00:34:43 +02:00
8d82fb6d8f added cx16 txt.scroll_right 2020-09-22 00:00:22 +02:00
306770331a added cx16 txt.scroll_left 2020-09-21 23:39:25 +02:00
d3f433c8cf specify VERA data port to use 2020-09-21 23:04:01 +02:00
cf49cbd1f8 more consistent about the system reset routine 2020-09-21 22:35:07 +02:00
8a99e75299 added cx16 txt.scroll_down 2020-09-21 22:06:48 +02:00
2dbf849c82 added cx16 txt.scroll_up 2020-09-21 21:39:36 +02:00
ba3dce0b4c optimized cx16 txt screen functions to use VERA autoincrement 2020-09-21 19:30:21 +02:00
ca9588380a added cx16 txt.clear_screencolors 2020-09-21 18:42:28 +02:00
ae2619602d lib renames in docs 2020-09-21 18:21:24 +02:00
de06353194 auto select correct library to import based on target, instead of having c64- and cx16- prefix variants
some programs are now 100% source compatible between C64 and Cx16 targets!
import libraries have been rena;med
2020-09-21 00:50:09 +02:00
3ff3f5e1cc compiler errors in standard format so that you can click on them in IDE to jump to the line 2020-09-20 22:24:35 +02:00
4b747859b3 types of constant values now actually follow their declared const var type 2020-09-20 01:14:53 +02:00
2201765366 mult fixes 2020-09-20 00:17:33 +02:00
dfa1d5e398 removed the ".w" word suffix (it confused the parser). 2020-09-19 23:27:40 +02:00
ce9a90f626 updates to make c16txtio more complete 2020-09-19 23:00:47 +02:00
2deb18beb2 tweaks to c64 txtio. Fixed expression evaluation of bitwise invert. 2020-09-19 22:37:24 +02:00
0f7454059c tweaks to c64 txtio 2020-09-19 22:10:33 +02:00
f9ba09ac4d todo 2020-09-19 17:39:46 +02:00
4e74873eae better swap() code 2020-09-19 17:32:29 +02:00
f0cd03d14f removed invalid duplicate name check about subroutine parameters 2020-09-19 16:04:04 +02:00
f2b069c562 correction, we don't allow address-of as a value for memory mapped vars, improved the error message instead 2020-09-19 15:54:42 +02:00
bc89306dc1 better detection of duplicate variable definitions 2020-09-19 15:46:51 +02:00
bf4da1655b doc 2020-09-18 23:57:40 +02:00
d819aa270f test 2020-09-18 23:38:50 +02:00
e6d945f835 doc 2020-09-18 23:35:02 +02:00
4fe408f1fd doc 2020-09-18 23:34:32 +02:00
c376e42092 implemented hidden line removal 2020-09-18 23:15:08 +02:00
63a653cdf0 preparing for hidden line removal 2020-09-18 22:51:44 +02:00
5d900800f2 vardecl value inits must not be shuffled around but stay at their original line at all times 2020-09-18 22:24:26 +02:00
def06dbc0b allow address-of to be used as a value for a memory pointer variable 2020-09-18 22:10:20 +02:00
9b66a597bb array literal const check added 2020-09-18 21:30:59 +02:00
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
4bfdbad2e4 added mandel gfx to examples 2020-07-03 23:56:36 +02:00
06137ecdc4 v2.3 2020-07-03 23:51:27 +02:00
d89f5b0df8 todo about fixing argclobbering 2020-07-03 23:49:17 +02:00
b6e2b36692 refactor 2020-07-03 23:37:38 +02:00
a6d789cfbc fixed function argument type cast bug 2020-07-03 17:24:43 +02:00
c07907e7bd fixed missing shifts codegen 2020-07-02 21:28:48 +02:00
7d8496c874 fixed missing shifts codegen 2020-07-02 19:18:47 +02:00
164ac56db1 compiler error todos 2020-07-01 22:31:38 +02:00
fdddb8ca64 slight optimization 2020-07-01 22:23:46 +02:00
a9d4b8b0fa fixed ast modifications on node arrays, in particular function call parameter lists 2020-07-01 22:03:54 +02:00
ec7b9f54c2 subroutine inlining is an optimizer step 2020-07-01 12:41:10 +02:00
307558a7e7 removed some double code related to call tree 2020-06-30 20:42:55 +02:00
febf423eab tehtriz compilation issues 2020-06-30 20:42:13 +02:00
a999c23014 simple subroutine inlining added 2020-06-27 17:03:03 +02:00
69f1ade595 gfx mandelbrot example added 2020-06-18 01:35:24 +02:00
b166576e54 comments 2020-06-17 23:27:54 +02:00
ee2ba5f398 some more optimizations for swap() function call asm code generation 2020-06-17 22:40:57 +02:00
cb9825484d some more optimized in-array assignments codegeneration 2020-06-17 21:41:38 +02:00
76cda82e23 v2.2 2020-06-16 01:43:44 +02:00
37b61d9e6b v2.2 2020-06-16 01:39:11 +02:00
52f0222a6d Got rid of old Ast transformer Api, some compiler error fixes 2020-06-16 01:25:49 +02:00
75ccac2f2c refactoring last of old Ast modification Api 2020-06-16 00:36:02 +02:00
5c771a91f7 refactoring last of old Ast modification Api 2020-06-14 16:56:48 +02:00
a242ad10e6 fix double printing of sub param vardecl 2020-06-14 13:46:46 +02:00
b5086b6a8f refactoring last of old Ast modification Api 2020-06-14 03:17:42 +02:00
3e47dad12a clearer no modifications 2020-06-14 02:54:29 +02:00
235610f40c refactored StatementOptimizer 2020-06-14 02:41:23 +02:00
6b59559c65 memory address assignment codegen 2020-06-14 02:12:40 +02:00
23e954f716 refactoring StatementOptimizer 2020-06-14 02:00:32 +02:00
983c899cad refactor AstIdentifierChecker 2020-06-13 00:14:19 +02:00
c2f9385965 refactor AstIdentifierChecker 2020-06-12 21:34:27 +02:00
ceb2c9e4f8 added string value assignment, leftstr, rightstr, substr functions 2020-06-06 00:05:39 +02:00
68a7f9c665 version 2.1 2020-06-04 23:03:18 +02:00
ffd8d9c7c1 more assignment expression optimizations 2020-06-04 22:57:32 +02:00
c66fc8630c fixed missing repeated constant folding in expression optimization 2020-06-04 20:22:37 +02:00
9ca1c66f2b added some optimizations for >= 0 and <0 comparisons for integers 2020-06-04 01:43:37 +02:00
33647a29d0 be smarter about certain implicit type casts 2020-06-03 23:55:41 +02:00
02b12cc762 optimized swap() for byte and word vars, optimized graphics line routine 2020-06-03 23:27:50 +02:00
3280993e2a stricter type checking in assignments (less implicit typecasts) 2020-06-02 22:36:57 +02:00
3723c22054 fix string param type 2020-06-02 02:09:52 +02:00
0a2c4ea0c4 improved ast printing 2020-06-02 01:51:27 +02:00
58a83c0439 improved code gen for passing string and array types. 2020-06-02 01:44:42 +02:00
d665489054 implemented asm for addressof-assignment 2020-06-02 00:31:56 +02:00
9200992024 slightly improved asm gen error messages 2020-06-02 00:31:20 +02:00
6408cc46a8 cmdrx16 github ref 2020-05-15 00:32:45 +02:00
961bcdb7ae some more todo's noted down 2020-05-15 00:24:25 +02:00
edee70cf31 use new api for ast mods in unused code remover 2020-05-15 00:16:53 +02:00
1978a9815a version 2.0 2020-05-14 23:59:18 +02:00
f5e6db9d66 big compiler speedup due to optimized scope lookups 2020-05-14 23:59:02 +02:00
a94bc40ab0 performance todo's 2020-05-08 20:41:10 +02:00
534b5ced8f updated the compiled examples 2020-04-10 23:36:29 +02:00
5ebd9b54e4 added some more optimized array assignments 2020-04-10 23:30:19 +02:00
cc4e272526 the new assignment code (once complete) really is a big enough change to bump the version to 2.0 2020-04-09 00:24:37 +02:00
295e199bfa optimized asm output for unneeded typecasts, fixed parent node linking issues with replaceChildNode, Assignment aug_op field is now mutable to avoid having to recreate many Assignment nodes 2020-04-09 00:12:50 +02:00
df3371b0f0 slight gfx optimizations 2020-04-08 22:53:23 +02:00
e4fe1d2b8d attempts to optimize in-place assignments 2020-04-08 03:11:38 +02:00
b8b9244ffa merged AddressOfInserter into StatementReorderer 2020-04-06 15:23:54 +02:00
3be3989e1c version 2020-04-06 14:31:23 +02:00
ed54cf680a fixed ast parent link bug in AstWalker, rewrote StatementReorderer using new API, when labels are sorted. 2020-04-06 14:31:02 +02:00
95e76058d3 version 2020-04-03 23:55:29 +02:00
a6bee6a860 some slight tweaks to asm for setting float value in array 2020-04-03 22:44:10 +02:00
d22780ee44 implemented asm for lsl array values 2020-04-03 21:45:52 +02:00
f8b0b9575d implemented asm for rol array values 2020-04-03 21:31:39 +02:00
4274fd168e implemented asm for rol2 array values 2020-04-03 21:24:55 +02:00
be7f5957f3 implemented asm for ror2 array values 2020-04-03 21:04:42 +02:00
f2e5d987a9 implemented asm for ror array values 2020-04-03 00:03:42 +02:00
f01173d8db fixed compilation of clear/set_carry() and clear/set_irqd() functions 2020-04-03 00:00:58 +02:00
15e8e0bf6d implemented asm for lsr array values 2020-04-02 23:38:45 +02:00
2c59cbdece fixed a crash in astchecking of array init values 2020-04-02 18:40:04 +02:00
b73da4ed02 some more obvious optimizations for X+X and X-X 2020-03-31 23:54:01 +02:00
267adb4612 doc 2020-03-29 03:06:51 +02:00
05c73fa8bc version 2020-03-28 17:06:59 +01:00
bfe9f442e6 balloon 2020-03-28 17:06:17 +01:00
0deadb694b updated the compiled examples 2020-03-28 14:31:31 +01:00
bed34378be doc 2020-03-28 14:24:00 +01:00
5927cf2d43 added turtle graphics example 2020-03-28 14:17:35 +01:00
fffe36e358 fix bresenham line 2020-03-28 13:42:24 +01:00
fac2a2d7cb fast asm plot 2020-03-28 00:36:44 +01:00
0af5582ca7 fix compiler crash for undefined symbol in expression 2020-03-27 23:09:46 +01:00
582d31263c better lines and circles 2020-03-27 00:09:17 +01:00
4108a528e1 proepr compiler error when there's no main module 2020-03-26 23:22:01 +01:00
ab7d7c2907 fix comparison of memory expressions (this error prevented some optimizations) 2020-03-26 22:59:42 +01:00
152888ee93 fix direcetmemoryread invalid asm 2020-03-26 22:46:05 +01:00
22f8f4f359 fixed memory pointer access asm code for direct reads and direct assignments 2020-03-26 19:20:39 +01:00
5f3a9e189a doc 2020-03-26 01:20:04 +01:00
b734dc44fd fix invalid assembly for @(address)++/-- 2020-03-26 01:13:20 +01:00
fab224f509 fix compiler crashing with invalid array initializer built from single integer 2020-03-25 01:23:54 +01:00
2f05ebb966 bitmap lines and circles 2020-03-25 01:07:42 +01:00
a335ba519a fix warnings about unreachable code 2020-03-24 22:37:42 +01:00
8805693ed2 cleaned up the way return statements are added to avoid code falling through in/out of subroutines 2020-03-24 22:02:50 +01:00
f2bb238e9b cleaned up various ast checks/mutations 2020-03-24 19:37:54 +01:00
131fe670a4 optimized scroll routines by removing needless twin loops 2020-03-24 17:33:47 +01:00
11e9539416 smooth scroll 2020-03-24 02:42:32 +01:00
3881ebe429 begun skramble clone 2020-03-24 01:47:02 +01:00
29d1b8802e whitespace 2020-03-24 00:24:51 +01:00
bcc75732e9 optimize asm jsr+rts into jmp 2020-03-23 23:51:27 +01:00
50a85ee6b0 attempt to optimize asm for bitshifts more. 2020-03-23 22:59:29 +01:00
2c7424fd43 fix: datatype mismatch in optimized bitshift const value 2020-03-23 22:35:23 +01:00
7426587c38 fix: add proper return statement type cast if needed, now also for non constant values 2020-03-23 19:49:11 +01:00
1f39749a5e tweak bitshift asm 2020-03-23 17:35:58 +01:00
ca63051c71 replaced todo's that aren't real todo's with regular exception 2020-03-23 13:00:44 +01:00
6dd44aaf0d compiler main cleanup 2020-03-23 02:54:04 +01:00
f89457ba68 fixed var initialization bug in anonymous scopes 2020-03-23 02:09:30 +01:00
efef205fcf doc 2020-03-23 01:24:54 +01:00
0c561d8528 fixed subroutine parameter value issue 2020-03-23 00:13:46 +01:00
8bfa2c4c02 proper initialization of block-level global variables 2020-03-22 22:47:05 +01:00
f0d4c3aba9 moved initialvalues to asmgen, fixed sgn bug and internal float 0.0 variable disappearing bug 2020-03-22 18:17:12 +01:00
3a99115070 Initial variable values semantics changed: now always sets value at program (re)start (except strings/arrays).
This may change later by introducing a compiler option to choose a strategy, perhaps.
2020-03-22 15:12:26 +01:00
7232134931 fix some compiler errors 2020-03-22 13:47:13 +01:00
954e911eb3 optimized zeros array initializer 2020-03-22 02:58:51 +01:00
63c073c93f got rid of the Simulator / AST VM 2020-03-22 02:50:34 +01:00
78feef9d59 simplified handling of initial vardecl values in codegeneration 2020-03-22 02:45:42 +01:00
4fbdd6d570 fix ubyte number print bug for 100-109 and 200-209 missing the tens digit 2020-03-22 01:49:05 +01:00
4929c198ba tweak error reporting, expanded lines and circles example 2020-03-22 00:43:46 +01:00
9409f17372 bugfixes in new optimization routines 2020-03-21 23:09:18 +01:00
43781c02d0 tweaked ast modifications 2020-03-21 18:42:40 +01:00
824f06e17f new var init values 2020-03-21 14:54:19 +01:00
21dbc6da97 doc 2020-03-21 12:51:32 +01:00
270ea54ff7 now properly compile assignment of struct literal value to struct variable (outside of vardecl) 2020-03-21 00:57:20 +01:00
771ac7aba7 error when struct literal value element count doesn't match struct members in assignment 2020-03-20 23:14:03 +01:00
97d36243f2 don't include the generated parser java files in git 2020-03-20 22:53:56 +01:00
511b47bac4 fix compiler crash when initializing struct var with something other than a struct literal 2020-03-20 22:48:33 +01:00
f265199fbe replaced typecastsAdder with version based on astwalker 2020-03-20 22:28:18 +01:00
a191ec71a4 this is not modifying the ast 2020-03-19 23:16:58 +01:00
82dce2dd53 added Foreverloop statement to the ast simulator 2020-03-19 22:45:27 +01:00
29ac160811 applying new astwalker for modifications 2020-03-19 22:40:49 +01:00
5e50ea14f8 applying new astwalker for modifications 2020-03-19 21:30:01 +01:00
40e6091506 new astvisitor tryout 2020-03-19 00:01:57 +01:00
0ee4d420b1 slight tweaks on the Ast, Program (the top level) is now a Node as well 2020-03-18 22:29:30 +01:00
66acce9e8e doc 2020-03-15 01:49:16 +01:00
6c23ae14ab ver 2020-03-15 01:37:01 +01:00
6f000d0d26 fix datatype warning 2020-03-15 01:14:44 +01:00
9d7eb3be5a fix error reporting of constantfolding, and number of errors printed 2020-03-15 01:10:08 +01:00
835555171e fix function call arg type mismatch crash 2020-03-15 00:50:59 +01:00
68ce4a1bf0 labels are now prefixed with underscore in assembly to fix undefined symbol errors from the assembler 2020-03-15 00:23:54 +01:00
a995867deb added check for duplicate label definitions 2020-03-15 00:16:50 +01:00
6bd99d63b4 cleanup of error reporting 2020-03-14 23:47:26 +01:00
baf5d3041a cleanup of error reporting 2020-03-14 23:15:44 +01:00
a326ffa00a added warning about sgn() of unsigned type 2020-03-14 21:09:34 +01:00
d28dd92b47 refreshed examples 2020-03-14 18:11:38 +01:00
1de328b2e8 added forever-loop and optimizer 2020-03-14 18:11:04 +01:00
51bb902162 added bresenham and circle example 2020-03-14 17:11:10 +01:00
4fd14f1366 doc updates 2020-03-14 15:20:04 +01:00
91d9559f79 avoid pulling in the dbus libraries for now 2020-03-14 14:40:39 +01:00
3245a9b157 restricted block to only directive/subroutine/vardecl/inlineasm 2020-03-14 14:20:55 +01:00
2b28493bba simplified module grammar rules 2020-03-14 13:44:13 +01:00
1382728bd2 warning about unreachable code after a return statement
added some dbus experiments for future compilation service
2020-03-14 13:12:01 +01:00
0422ad080a added exit function to astvm simulator 2020-03-13 02:44:01 +01:00
64d682bfde todo 2020-03-13 02:33:02 +01:00
b182f7e693 optimizer removes unreachable code following call to exit() 2020-03-13 02:31:53 +01:00
e6be428589 compiler warning for unreachable code following a call to exit() 2020-03-13 02:21:37 +01:00
85c7f8314b added exit(rc) builtin function to immediately exit the program with a return code in A register 2020-03-13 02:08:18 +01:00
796d07a7f8 fix crash in asm code generated for bitshift operation with memory address operand 2020-03-13 01:26:53 +01:00
2af86a10b2 remove stack error comments 2020-03-13 00:52:52 +01:00
7fbe486dff fix eval stack register X error in print_uw 2020-03-13 00:50:30 +01:00
87e5a9859a remove autogenerated labels from vice mon list, fixes #17 2020-03-12 22:33:58 +01:00
b036e5ed72 refreshed the compiled examples 2020-03-12 01:14:10 +01:00
5f1ec80ae0 improved array literal datatype handling, fixed some datatype compiler errors related to this 2020-03-12 01:10:19 +01:00
fbecedaf41 added error for unsupported sort(floatarray) 2020-03-11 23:33:06 +01:00
aa36acd65a implemented reverse(floatarray) builtin function 2020-03-11 23:18:03 +01:00
8d1a4588d3 added 'downto' range expression 2020-03-11 20:59:14 +01:00
66d2af4453 added '@' alternative string/char encoding 2020-03-11 00:41:58 +01:00
ef6c731bb3 added '@' alternative string/char encoding 2020-03-11 00:32:50 +01:00
98a638a2f3 split asmsub and romsub declarations 2020-03-10 23:09:31 +01:00
96d8a7f0d7 float assembly code moved to separate library file 2020-03-10 22:03:24 +01:00
3162b10392 optimize callgraph 2020-03-10 21:47:15 +01:00
e2358de27c ver 2020-03-10 20:39:30 +01:00
7facb4f372 correct version 1.70 2020-02-09 01:41:05 +01:00
ee90fed489 readme 2020-02-09 01:33:20 +01:00
4796c56c35 antlr code back 2020-02-09 01:29:58 +01:00
e2cb031386 added 'void' keyword to explicitly ignore subroutine return values (and no longer get a warning) 2020-02-09 01:29:09 +01:00
a0bc97b90c fix byte array iteration for bb in [1,2,3]
improved array literal datatype detection
2020-02-09 00:45:53 +01:00
fd240899bd fix CHROUT in simulator 2020-02-09 00:12:50 +01:00
885b22df40 fixed while and repeat warning messages line number
fixed invalid while and repeat asm label names
fixed boolean checking of numbers
2020-02-08 19:45:30 +01:00
11de3db25f simplified heapId for arrayvalues 2020-02-08 18:49:48 +01:00
14a13da7ec simplified heapId for stringvalue 2020-02-08 15:54:03 +01:00
875a71c786 removed datatype from StringValue classes (is always STR now) 2020-02-08 02:21:18 +01:00
0ff5b79353 code inspection cleanups 2020-02-08 01:31:41 +01:00
8c4d276810 improvements to string encoding/decoding and text output in the simulator 2020-02-08 01:12:30 +01:00
3dd38c0ac8 antlr library updated to 4.8 2020-02-07 23:58:07 +01:00
b8816a0e2f got rid of separate str_s datatype 2020-02-07 20:47:38 +01:00
a01a9e76f9 removed bogus clang target
fixed various simulator bugs regarding strings and chars
2020-02-07 01:22:07 +01:00
357d704aec clean up version specifier 2020-02-02 19:33:40 +01:00
868df1865c got rid of obsolete code 2020-02-02 19:18:40 +01:00
654d74da1e automatic selection of best Vice C64 emulator executable 2020-02-02 13:39:56 +01:00
59939c727a gradle updated 2020-02-02 13:39:25 +01:00
fbcf190324 sync gradle version with my manjaro packaged gradle 2020-01-27 21:32:42 +01:00
b9922a90cc update gradle wrapper to 6.1.1 2020-01-26 18:36:51 +01:00
66e0b07428 gradle updates 2020-01-07 01:29:25 +01:00
01e617ae8f new kotlin version 2019-12-09 16:17:20 +01:00
52769decd4 fix assembler float truncation warning 2019-11-27 22:36:59 +01:00
165eec4054 started a c++ language compiler code target
(meant to be an intermediate step before direct Wasm/binaryen, via clang compilation to wasm)
2019-10-30 00:15:03 +01:00
8c2e602cc7 preparing for multiple compiler backends/targets 2019-10-26 23:41:15 +02:00
245 changed files with 32264 additions and 27898 deletions

13
.gitignore vendored
View File

@ -1,8 +1,8 @@
.idea/workspace.xml
.idea/discord.xml
/build/
/dist/
/output/
build/
dist/
output/
.*cache/
*.directory
*.prg
@ -12,9 +12,9 @@
*.vice-mon-list
docs/build
out/
**/*.interp
**/*.tokens
parser/**/*.interp
parser/**/*.tokens
parser/**/*.java
*.py[cod]
*.egg
*.egg-info
@ -28,5 +28,4 @@ parsetab.py
.attach_pid*
.gradle
build/
/prog8compiler.jar

9
.idea/libraries/antlr_4_8_complete.xml generated Normal file
View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="antlr-4.8-complete">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.8-complete.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

9
.idea/libraries/antlr_runtime_4_8.xml generated Normal file
View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="antlr-runtime-4.8">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

29
.idea/markdown-navigator-enh.xml generated Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownEnhProjectSettings">
<AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imagePathType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
<component name="MarkdownNavigatorHistory">
<PasteImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30">
<highlightList />
<directories />
<filenames />
</PasteImageHistory>
<CopyImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30">
<highlightList />
<directories />
<filenames />
</CopyImageHistory>
<PasteLinkHistory onPasteImageTargetRef="3" onPasteTargetRef="1" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteWikiElement="2" onPasteReferenceElement="2" hideInapplicableOperations="false" preserveLinkFormat="false" useHeadingForLinkText="false" linkFormat="0" saveAsDefaultOnOK="false" />
<TableToJsonHistory>
<entries />
</TableToJsonHistory>
<TableSortHistory>
<entries />
</TableSortHistory>
</component>
</project>

57
.idea/markdown-navigator.xml generated Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FlexmarkProjectSettings">
<FlexmarkHtmlSettings flexmarkSpecExampleRendering="0" flexmarkSpecExampleRenderHtml="false">
<flexmarkSectionLanguages>
<option name="1" value="Markdown" />
<option name="2" value="HTML" />
<option name="3" value="flexmark-ast:1" />
</flexmarkSectionLanguages>
</FlexmarkHtmlSettings>
</component>
<component name="MarkdownProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true" lastLayoutSetsDefault="false">
<PanelProvider>
<provider providerId="com.vladsch.md.nav.editor.javafx.html.panel" providerName="JavaFX WebView" />
</PanelProvider>
</PreviewSettings>
<ParserSettings gitHubSyntaxChange="false" correctedInvalidSettings="false" emojiShortcuts="1" emojiImages="0">
<PegdownExtensions>
<option name="ANCHORLINKS" value="true" />
<option name="ATXHEADERSPACE" value="true" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="INTELLIJ_DUMMY_IDENTIFIER" value="true" />
<option name="RELAXEDHRULES" value="true" />
<option name="STRIKETHROUGH" value="true" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
</PegdownExtensions>
<ParserOptions>
<option name="COMMONMARK_LISTS" value="true" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="PRODUCTION_SPEC_PARSER" value="true" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" addPageHeader="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" plantUmlConversion="0">
<GeneratorProvider>
<provider providerId="com.vladsch.md.nav.editor.javafx.html.generator" providerName="JavaFx HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.md.nav.editor.javafx.html.css" providerName="Default JavaFx Stylesheet" />
</StylesheetProvider>
<ScriptProviders>
<provider providerId="com.vladsch.md.nav.editor.hljs.html.script" providerName="HighlightJS Script" />
</ScriptProviders>
<cssText />
<cssUriHistory />
</CssSettings>
</component>
</project>

18
.idea/misc.xml generated
View File

@ -1,6 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<component name="ANTLRGenerationPreferences">
<option name="perGrammarGenerationSettings">
<list>
<PerGrammarGenerationSettings>
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/prog8.g4" />
<option name="autoGen" value="true" />
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
<option name="libDir" value="" />
<option name="encoding" value="" />
<option name="pkg" value="" />
<option name="language" value="" />
<option name="generateListener" value="false" />
</PerGrammarGenerationSettings>
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

1
.idea/modules.xml generated
View File

@ -2,7 +2,6 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/OldCodeGen/OldCodeGen.iml" filepath="$PROJECT_DIR$/OldCodeGen/OldCodeGen.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />

View File

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

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>

View File

@ -1,762 +0,0 @@
package oldcodegen
/** OLD STACK-VM CODE GEN -- NO LONGER USED **/
// note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles
// possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles
import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.initvarsSubName
import prog8.ast.statements.ZeropageWish
import prog8.compiler.*
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import java.io.File
import kotlin.math.abs
class AssemblyError(msg: String) : RuntimeException(msg)
internal fun intVal(valueInstr: Instruction) = valueInstr.arg!!.integerValue()
internal fun hexVal(valueInstr: Instruction) = valueInstr.arg!!.integerValue().toHex()
internal fun hexValPlusOne(valueInstr: Instruction) = (valueInstr.arg!!.integerValue()+1).toHex()
internal fun getFloatConst(value: RuntimeValue): String =
globalFloatConsts[value.numericValue().toDouble()]
?: throw AssemblyError("should have a global float const for number $value")
internal val globalFloatConsts = mutableMapOf<Double, String>()
internal fun signExtendA(into: String) =
"""
ora #$7f
bmi +
lda #0
+ sta $into
"""
class AsmGen(private val options: CompilationOptions, private val program: IntermediateProgram,
private val heap: HeapValues, private val zeropage: Zeropage) {
private val assemblyLines = mutableListOf<String>()
private lateinit var block: IntermediateProgram.ProgramBlock
init {
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
for(block in program.blocks) {
val newvars = block.variables.map { IntermediateProgram.Variable(symname(it.scopedname, block), it.value, it.params) }.toMutableList()
val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap()
val newinstructions = block.instructions.asSequence().map {
when {
it is LabelInstr -> LabelInstr(symname(it.name, block), it.asmProc)
it.opcode == Opcode.INLINE_ASSEMBLY -> it
else ->
Instruction(it.opcode, it.arg, it.arg2,
callLabel = if (it.callLabel != null) symname(it.callLabel, block) else null,
callLabel2 = if (it.callLabel2 != null) symname(it.callLabel2, block) else null)
}
}.toMutableList()
val newMempointers = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
val newblock = IntermediateProgram.ProgramBlock(
block.name,
block.address,
newinstructions,
newvars,
newMempointers,
newlabels,
force_output = block.force_output)
newblocks.add(newblock)
}
program.blocks.clear()
program.blocks.addAll(newblocks)
val newAllocatedZp = program.allocatedZeropageVariables.map { symname(it.key, null) to it.value}
program.allocatedZeropageVariables.clear()
program.allocatedZeropageVariables.putAll(newAllocatedZp)
// make a list of all const floats that are used
for(block in program.blocks) {
for(ins in block.instructions.filter{it.arg?.type== DataType.FLOAT}) {
val float = ins.arg!!.numericValue().toDouble()
if(float !in globalFloatConsts)
globalFloatConsts[float] = "prog8_const_float_${globalFloatConsts.size}"
}
}
}
fun compileToAssembly(optimize: Boolean): AssemblyProgram {
println("Generating assembly code from intermediate code... ")
assemblyLines.clear()
header()
for(b in program.blocks)
block2asm(b)
if(optimize) {
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
}
File("${program.name}.asm").printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
return AssemblyProgram(program.name)
}
private fun out(str: String, splitlines: Boolean=true) {
if(splitlines) {
for (line in str.split('\n')) {
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
assemblyLines.add(trimmed)
}
} else assemblyLines.add(str)
}
// convert a fully scoped name (defined in the given block) to a valid assembly symbol name
private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String {
if(' ' in scoped)
return scoped
val blockLocal: Boolean
var name = if (block!=null && scoped.startsWith("${block.name}.")) {
blockLocal = true
scoped.substring(block.name.length+1)
}
else {
blockLocal = false
scoped
}
name = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
if(name=="-")
return "-"
if(blockLocal)
name = name.replace(".", "_")
else {
val parts = name.split(".", limit=2)
if(parts.size>1)
name = "${parts[0]}.${parts[1].replace(".", "_")}"
}
return name.replace("-", "")
}
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
val b0 = "$"+flt.b0.toString(16).padStart(2, '0')
val b1 = "$"+flt.b1.toString(16).padStart(2, '0')
val b2 = "$"+flt.b2.toString(16).padStart(2, '0')
val b3 = "$"+flt.b3.toString(16).padStart(2, '0')
val b4 = "$"+flt.b4.toString(16).padStart(2, '0')
return "$b0, $b1, $b2, $b3, $b4"
}
private fun header() {
val ourName = this.javaClass.name
out("; 6502 assembly code for '${program.name}'")
out("; generated by $ourName on ${Date()}")
out("; assembler syntax is for the 64tasm cross-assembler")
out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
out("\n.cpu '6502'\n.enc 'none'\n")
if(program.loadAddress==0) // fix load address
program.loadAddress = if(options.launcher==LauncherType.BASIC)
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
when {
options.launcher == LauncherType.BASIC -> {
if (program.loadAddress != 0x0801)
throw AssemblyError("BASIC output must have load address $0801")
out("; ---- basic program with sys call ----")
out("* = ${program.loadAddress.toHex()}")
val year = Calendar.getInstance().get(Calendar.YEAR)
out(" .word (+), $year")
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.loadAddress.toHex()}\n")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.RAW -> {
out("; ---- raw assembler program ----")
out("* = ${program.loadAddress.toHex()}\n")
}
}
if(zeropage.exitProgramStrategy!=Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
// disable shift-commodore charset switching and run/stop key
out(" lda #$80")
out(" lda #$80")
out(" sta 657\t; disable charset switching")
out(" lda #239")
out(" sta 808\t; disable run/stop key")
}
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block")
for(block in program.blocks) {
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name== initvarsSubName } as? LabelInstr
if(initVarsLabel!=null)
out(" jsr ${block.name}.${initVarsLabel.name}")
}
out(" clc")
when(zeropage.exitProgramStrategy) {
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
out(" jmp main.start\t; jump to program entrypoint")
}
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
out(" jsr main.start\t; call program entrypoint")
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out("")
// the global list of all floating point constants for the whole program
for(flt in globalFloatConsts) {
val floatFill = makeFloatFill(MachineDefinition.Mflpt5.fromNumber(flt.key))
out("${flt.value}\t.byte $floatFill ; float ${flt.key}")
}
}
private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
block = blk
out("\n\n; ---- block: '${block.name}' ----")
if(!blk.force_output)
out("${block.name}\t.proc\n")
if(block.address!=null) {
out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'")
out("* = ${block.address?.toHex()}")
}
// deal with zeropage variables
for(variable in blk.variables) {
val sym = symname(blk.name+"."+variable.scopedname, null)
val zpVar = program.allocatedZeropageVariables[sym]
if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
if(variable.params.zp != ZeropageWish.NOT_IN_ZEROPAGE &&
variable.value.type in zeropage.allowedDatatypes
&& variable.value.type != DataType.FLOAT) {
try {
val address = zeropage.allocate(sym, variable.value.type, null)
out("${variable.scopedname} = $address\t; auto zp ${variable.value.type}")
// make sure we add the var to the set of zpvars for this block
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
} catch (x: ZeropageDepletedError) {
// leave it as it is.
}
}
}
else {
// it was already allocated on the zp
out("${variable.scopedname} = ${zpVar.first}\t; zp ${zpVar.second}")
}
}
out("\n; memdefs and kernel subroutines")
memdefs2asm(block)
out("\n; non-zeropage variables")
vardecls2asm(block)
out("")
val instructionPatternWindowSize = 8 // increase once patterns occur longer than this.
var processed = 0
for (ins in block.instructions.windowed(instructionPatternWindowSize, partialWindows = true)) {
if (processed == 0) {
processed = instr2asm(ins)
if (processed == 0) {
// the instructions are not recognised yet and can't be translated into assembly
throw CompilerException("no asm translation found for instruction pattern: $ins")
}
}
processed--
}
if(!blk.force_output)
out("\n\t.pend\n")
}
private fun memdefs2asm(block: IntermediateProgram.ProgramBlock) {
for(m in block.memoryPointers) {
out(" ${m.key} = ${m.value.first.toHex()}")
}
}
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
val uniqueNames = block.variables.map { it.scopedname }.toSet()
if (uniqueNames.size != block.variables.size)
throw AssemblyError("not all variables have unique names")
// these are the non-zeropage variables.
// first get all the flattened struct members, they MUST remain in order
out("; flattened struct members")
val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null }
structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) }
// sort the other variables by type
out("; other variables sorted by type")
val sortedVars = normalVars.sortedBy { it.value.type }
for (variable in sortedVars) {
val sym = symname(block.name + "." + variable.scopedname, null)
if(sym in program.allocatedZeropageVariables)
continue // skip the ones that already belong in the zero page
vardecl2asm(variable.scopedname, variable.value, variable.params)
}
}
private fun vardecl2asm(varname: String, value: RuntimeValue, parameters: IntermediateProgram.VariableParameters) {
when (value.type) {
DataType.UBYTE -> out("$varname\t.byte 0")
DataType.BYTE -> out("$varname\t.char 0")
DataType.UWORD -> out("$varname\t.word 0")
DataType.WORD -> out("$varname\t.sint 0")
DataType.FLOAT -> out("$varname\t.byte 0,0,0,0,0 ; float")
DataType.STR, DataType.STR_S -> {
val rawStr = heap.get(value.heapId!!).str!!
val bytes = encodeStr(rawStr, value.type).map { "$" + it.toString(16).padStart(2, '0') }
out("$varname\t; ${value.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
for (chunk in bytes.chunked(16))
out(" .byte " + chunk.joinToString())
}
DataType.ARRAY_UB -> {
// unsigned integer byte arraysize
val data = makeArrayFillDataUnsigned(value)
if (data.size <= 16)
out("$varname\t.byte ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .byte " + chunk.joinToString())
}
}
DataType.ARRAY_B -> {
// signed integer byte arraysize
val data = makeArrayFillDataSigned(value)
if (data.size <= 16)
out("$varname\t.char ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .char " + chunk.joinToString())
}
}
DataType.ARRAY_UW -> {
// unsigned word arraysize
val data = makeArrayFillDataUnsigned(value)
if (data.size <= 16)
out("$varname\t.word ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .word " + chunk.joinToString())
}
}
DataType.ARRAY_W -> {
// signed word arraysize
val data = makeArrayFillDataSigned(value)
if (data.size <= 16)
out("$varname\t.sint ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .sint " + chunk.joinToString())
}
}
DataType.ARRAY_F -> {
// float arraysize
val array = heap.get(value.heapId!!).doubleArray!!
val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) }
out(varname)
for (f in array.zip(floatFills))
out(" .byte ${f.second} ; float ${f.first}")
}
DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened")
}
}
private fun encodeStr(str: String, dt: DataType): List<Short> {
return when(dt) {
DataType.STR -> {
val bytes = Petscii.encodePetscii(str, true)
bytes.plus(0)
}
DataType.STR_S -> {
val bytes = Petscii.encodeScreencode(str, true)
bytes.plus(0)
}
else -> throw AssemblyError("invalid str type")
}
}
private fun makeArrayFillDataUnsigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId!!).array!!
return when {
value.type== DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map { "$"+it.integer!!.toString(16).padStart(2, '0') }
value.type== DataType.ARRAY_UW -> array.map {
when {
it.integer!=null -> "$"+it.integer.toString(16).padStart(2, '0')
it.addressOf!=null -> symname(it.addressOf.scopedname!!, block)
else -> throw AssemblyError("weird type in array")
}
}
else -> throw AssemblyError("invalid arraysize type")
}
}
private fun makeArrayFillDataSigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId!!).array!!
// note: array of signed value can never contain pointer-to type, so simply accept values as being all integers
return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W) {
array.map {
if(it.integer!!>=0)
"$"+it.integer.toString(16).padStart(2, '0')
else
"-$"+abs(it.integer).toString(16).padStart(2, '0')
}
}
else throw AssemblyError("invalid arraysize type")
}
private fun instr2asm(ins: List<Instruction>): Int {
// find best patterns (matching the most of the lines, then with the smallest weight)
val fragments = findPatterns(ins).sortedByDescending { it.segmentSize }
if(fragments.isEmpty()) {
// we didn't find any matching patterns (complex multi-instruction fragments), try simple ones
val firstIns = ins[0]
val singleAsm = simpleInstr2Asm(firstIns, block)
if(singleAsm != null) {
outputAsmFragment(singleAsm)
return 1
}
return 0
}
val best = fragments[0]
outputAsmFragment(best.asm)
return best.segmentSize
}
private fun outputAsmFragment(singleAsm: String) {
if (singleAsm.isNotEmpty()) {
if(singleAsm.startsWith("@inline@"))
out(singleAsm.substring(8), false)
else {
val withNewlines = singleAsm.replace('|', '\n')
out(withNewlines)
}
}
}
private fun findPatterns(segment: List<Instruction>): List<AsmFragment> {
val opcodes = segment.map { it.opcode }
val result = mutableListOf<AsmFragment>()
// check for operations that modify a single value, by putting it on the stack (and popping it afterwards)
if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[2]==Opcode.POP_VAR_BYTE) ||
(opcodes[0]==Opcode.PUSH_VAR_WORD && opcodes[2]==Opcode.POP_VAR_WORD) ||
(opcodes[0]==Opcode.PUSH_VAR_FLOAT && opcodes[2]==Opcode.POP_VAR_FLOAT)) {
if (segment[0].callLabel == segment[2].callLabel) {
val fragment = sameVarOperation(segment[0].callLabel!!, segment[1])
if (fragment != null) {
fragment.segmentSize = 3
result.add(fragment)
}
}
}
else if((opcodes[0]==Opcode.PUSH_BYTE && opcodes[1] in setOf(Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB,
Opcode.INC_INDEXED_VAR_UW, Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_W,
Opcode.DEC_INDEXED_VAR_UW, Opcode.DEC_INDEXED_VAR_FLOAT))) {
val fragment = sameConstantIndexedVarOperation(segment[1].callLabel!!, segment[0].arg!!.integerValue(), segment[1])
if(fragment!=null) {
fragment.segmentSize=2
result.add(fragment)
}
}
else if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1] in setOf(Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB,
Opcode.INC_INDEXED_VAR_UW, Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_W,
Opcode.DEC_INDEXED_VAR_UW, Opcode.DEC_INDEXED_VAR_FLOAT))) {
val fragment = sameIndexedVarOperation(segment[1].callLabel!!, segment[0].callLabel!!, segment[1])
if(fragment!=null) {
fragment.segmentSize=2
result.add(fragment)
}
}
else if((opcodes[0]==Opcode.PUSH_MEM_UB && opcodes[2]==Opcode.POP_MEM_BYTE) ||
(opcodes[0]==Opcode.PUSH_MEM_B && opcodes[2]==Opcode.POP_MEM_BYTE) ||
(opcodes[0]==Opcode.PUSH_MEM_UW && opcodes[2]==Opcode.POP_MEM_WORD) ||
(opcodes[0]==Opcode.PUSH_MEM_W && opcodes[2]==Opcode.POP_MEM_WORD) ||
(opcodes[0]==Opcode.PUSH_MEM_FLOAT && opcodes[2]==Opcode.POP_MEM_FLOAT)) {
if(segment[0].arg==segment[2].arg) {
val fragment = sameMemOperation(segment[0].arg!!.integerValue(), segment[1])
if(fragment!=null) {
fragment.segmentSize = 3
result.add(fragment)
}
}
}
else if((opcodes[0]==Opcode.PUSH_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_BYTE &&
opcodes[3]==Opcode.PUSH_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_BYTE) ||
(opcodes[0]==Opcode.PUSH_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_WORD &&
opcodes[3]==Opcode.PUSH_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_WORD)) {
if(segment[0].arg==segment[3].arg && segment[1].callLabel==segment[4].callLabel) {
val fragment = sameConstantIndexedVarOperation(segment[1].callLabel!!, segment[0].arg!!.integerValue(), segment[2])
if(fragment!=null){
fragment.segmentSize = 5
result.add(fragment)
}
}
}
else if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_BYTE &&
opcodes[3]==Opcode.PUSH_VAR_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_BYTE) ||
(opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_WORD &&
opcodes[3]==Opcode.PUSH_VAR_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_WORD)) {
if(segment[0].callLabel==segment[3].callLabel && segment[1].callLabel==segment[4].callLabel) {
val fragment = sameIndexedVarOperation(segment[1].callLabel!!, segment[0].callLabel!!, segment[2])
if(fragment!=null){
fragment.segmentSize = 5
result.add(fragment)
}
}
}
// add any matching patterns from the big list
for(pattern in Patterns.patterns) {
if(pattern.sequence.size > segment.size || (pattern.altSequence!=null && pattern.altSequence.size > segment.size))
continue // don't accept patterns that don't fit
val opcodesList = opcodes.subList(0, pattern.sequence.size)
if(pattern.sequence == opcodesList) {
val asm = pattern.asm(segment)
if(asm!=null)
result.add(AsmFragment(asm, pattern.sequence.size))
} else if(pattern.altSequence!=null) {
val opcodesListAlt = opcodes.subList(0, pattern.altSequence.size)
if(pattern.altSequence == opcodesListAlt) {
val asm = pattern.asm(segment)
if (asm != null)
result.add(AsmFragment(asm, pattern.sequence.size))
}
}
}
return result
}
private fun sameConstantIndexedVarOperation(variable: String, index: Int, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-value / op / push-index-value / pop-into-indexed-var
return when(ins.opcode) {
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
Opcode.SHR_SBYTE -> AsmFragment(" lda $variable+$index | asl a | ror $variable+$index")
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index * 2 + 1} | asl a | ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL_BYTE -> AsmFragment(" rol $variable+$index", 8)
Opcode.ROR_BYTE -> AsmFragment(" ror $variable+$index", 8)
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL2_BYTE -> AsmFragment(" lda $variable+$index | cmp #\$80 | rol $variable+$index", 8)
Opcode.ROR2_BYTE -> AsmFragment(" lda $variable+$index | lsr a | bcc + | ora #\$80 |+ | sta $variable+$index", 10)
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2} | bcc + | inc $variable+${index * 2 + 1} |+", 20)
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2} | bcc + | lda $variable+${index * 2 + 1} | ora #\$80 | sta $variable+${index * 2 + 1} |+", 30)
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" inc $variable+$index", 2)
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" dec $variable+$index", 5)
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment(" inc $variable+${index * 2} | bne + | inc $variable+${index * 2 + 1} |+")
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index * 2} | bne + | dec $variable+${index * 2 + 1} |+ | dec $variable+${index * 2}")
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(
"""
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.inc_var_f
""")
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(
"""
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.dec_var_f
""")
else -> null
}
}
private fun sameIndexedVarOperation(variable: String, indexVar: String, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-value / op / push-index-var / pop-into-indexed-var
val saveX = " stx ${MachineDefinition.C64Zeropage.SCRATCH_B1} |"
val restoreX = " | ldx ${MachineDefinition.C64Zeropage.SCRATCH_B1}"
val loadXWord: String
val loadX: String
when(indexVar) {
"X" -> {
loadX = ""
loadXWord = " txa | asl a | tax |"
}
"Y" -> {
loadX = " tya | tax |"
loadXWord = " tya | asl a | tax |"
}
"A" -> {
loadX = " tax |"
loadXWord = " asl a | tax |"
}
else -> {
// the indexvar is a real variable, not a register
loadX = " ldx $indexVar |"
loadXWord = " lda $indexVar | asl a | tax |"
}
}
return when (ins.opcode) {
Opcode.SHL_BYTE -> AsmFragment(" txa | $loadX asl $variable,x | tax", 10)
Opcode.SHR_UBYTE -> AsmFragment(" txa | $loadX lsr $variable,x | tax", 10)
Opcode.SHR_SBYTE -> AsmFragment("$saveX $loadX lda $variable,x | asl a | ror $variable,x $restoreX", 10)
Opcode.SHL_WORD -> AsmFragment("$saveX $loadXWord asl $variable,x | rol $variable+1,x $restoreX", 10)
Opcode.SHR_UWORD -> AsmFragment("$saveX $loadXWord lsr $variable+1,x | ror $variable,x $restoreX", 10)
Opcode.SHR_SWORD -> AsmFragment("$saveX $loadXWord lda $variable+1,x | asl a | ror $variable+1,x | ror $variable,x $restoreX", 10)
Opcode.ROL_BYTE -> AsmFragment(" txa | $loadX rol $variable,x | tax", 10)
Opcode.ROR_BYTE -> AsmFragment(" txa | $loadX ror $variable,x | tax", 10)
Opcode.ROL_WORD -> AsmFragment("$saveX $loadXWord rol $variable,x | rol $variable+1,x $restoreX", 10)
Opcode.ROR_WORD -> AsmFragment("$saveX $loadXWord ror $variable+1,x | ror $variable,x $restoreX", 10)
Opcode.ROL2_BYTE -> AsmFragment("$saveX $loadX lda $variable,x | cmp #\$80 | rol $variable,x $restoreX", 10)
Opcode.ROR2_BYTE -> AsmFragment("$saveX $loadX lda $variable,x | lsr a | bcc + | ora #\$80 |+ | sta $variable,x $restoreX", 10)
Opcode.ROL2_WORD -> AsmFragment(" txa | $loadXWord asl $variable,x | rol $variable+1,x | bcc + | inc $variable,x |+ | tax", 30)
Opcode.ROR2_WORD -> AsmFragment("$saveX $loadXWord lsr $variable+1,x | ror $variable,x | bcc + | lda $variable+1,x | ora #\$80 | sta $variable+1,x |+ $restoreX", 30)
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" txa | $loadX inc $variable,x | tax", 10)
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" txa | $loadX dec $variable,x | tax", 10)
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment("$saveX $loadXWord inc $variable,x | bne + | inc $variable+1,x |+ $restoreX", 10)
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment("$saveX $loadXWord lda $variable,x | bne + | dec $variable+1,x |+ | dec $variable,x $restoreX", 10)
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(" lda #<$variable | ldy #>$variable | $saveX $loadX jsr c64flt.inc_indexed_var_f $restoreX")
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(" lda #<$variable | ldy #>$variable | $saveX $loadX jsr c64flt.dec_indexed_var_f $restoreX")
else -> null
}
}
private fun sameMemOperation(address: Int, ins: Instruction): AsmFragment? {
// an in place operation that consists of push-mem / op / pop-mem
val addr = address.toHex()
val addrHi = (address+1).toHex()
return when(ins.opcode) {
Opcode.SHL_BYTE -> AsmFragment(" asl $addr", 10)
Opcode.SHR_UBYTE -> AsmFragment(" lsr $addr", 10)
Opcode.SHR_SBYTE -> AsmFragment(" lda $addr | asl a | ror $addr", 10)
Opcode.SHL_WORD -> AsmFragment(" asl $addr | rol $addrHi", 10)
Opcode.SHR_UWORD -> AsmFragment(" lsr $addrHi | ror $addr", 10)
Opcode.SHR_SWORD -> AsmFragment(" lda $addrHi | asl a | ror $addrHi | ror $addr", 10)
Opcode.ROL_BYTE -> AsmFragment(" rol $addr", 10)
Opcode.ROR_BYTE -> AsmFragment(" ror $addr", 10)
Opcode.ROL_WORD -> AsmFragment(" rol $addr | rol $addrHi", 10)
Opcode.ROR_WORD -> AsmFragment(" ror $addrHi | ror $addr", 10)
Opcode.ROL2_BYTE -> AsmFragment(" lda $addr | cmp #\$80 | rol $addr", 10)
Opcode.ROR2_BYTE -> AsmFragment(" lda $addr | lsr a | bcc + | ora #\$80 |+ | sta $addr", 10)
Opcode.ROL2_WORD -> AsmFragment(" lda $addr | cmp #\$80 | rol $addr | rol $addrHi", 10)
Opcode.ROR2_WORD -> AsmFragment(" lsr $addrHi | ror $addr | bcc + | lda $addrHi | ora #$80 | sta $addrHi |+", 20)
else -> null
}
}
private fun sameVarOperation(variable: String, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-var / op / pop-var
return when(ins.opcode) {
Opcode.SHL_BYTE -> {
when (variable) {
"A" -> AsmFragment(" asl a", 10)
"X" -> AsmFragment(" txa | asl a | tax", 10)
"Y" -> AsmFragment(" tya | asl a | tay", 10)
else -> AsmFragment(" asl $variable", 10)
}
}
Opcode.SHR_UBYTE -> {
when (variable) {
"A" -> AsmFragment(" lsr a", 10)
"X" -> AsmFragment(" txa | lsr a | tax", 10)
"Y" -> AsmFragment(" tya | lsr a | tay", 10)
else -> AsmFragment(" lsr $variable", 10)
}
}
Opcode.SHR_SBYTE -> {
// arithmetic shift right (keep sign bit)
when (variable) {
"A" -> AsmFragment(" cmp #$80 | ror a", 10)
"X" -> AsmFragment(" txa | cmp #$80 | ror a | tax", 10)
"Y" -> AsmFragment(" tya | cmp #$80 | ror a | tay", 10)
else -> AsmFragment(" lda $variable | asl a | ror $variable", 10)
}
}
Opcode.SHL_WORD -> {
AsmFragment(" asl $variable | rol $variable+1", 10)
}
Opcode.SHR_UWORD -> {
AsmFragment(" lsr $variable+1 | ror $variable", 10)
}
Opcode.SHR_SWORD -> {
// arithmetic shift right (keep sign bit)
AsmFragment(" lda $variable+1 | asl a | ror $variable+1 | ror $variable", 10)
}
Opcode.ROL_BYTE -> {
when (variable) {
"A" -> AsmFragment(" rol a", 10)
"X" -> AsmFragment(" txa | rol a | tax", 10)
"Y" -> AsmFragment(" tya | rol a | tay", 10)
else -> AsmFragment(" rol $variable", 10)
}
}
Opcode.ROR_BYTE -> {
when (variable) {
"A" -> AsmFragment(" ror a", 10)
"X" -> AsmFragment(" txa | ror a | tax", 10)
"Y" -> AsmFragment(" tya | ror a | tay", 10)
else -> AsmFragment(" ror $variable", 10)
}
}
Opcode.ROL_WORD -> {
AsmFragment(" rol $variable | rol $variable+1", 10)
}
Opcode.ROR_WORD -> {
AsmFragment(" ror $variable+1 | ror $variable", 10)
}
Opcode.ROL2_BYTE -> { // 8-bit rol
when (variable) {
"A" -> AsmFragment(" cmp #\$80 | rol a", 10)
"X" -> AsmFragment(" txa | cmp #\$80 | rol a | tax", 10)
"Y" -> AsmFragment(" tya | cmp #\$80 | rol a | tay", 10)
else -> AsmFragment(" lda $variable | cmp #\$80 | rol $variable", 10)
}
}
Opcode.ROR2_BYTE -> { // 8-bit ror
when (variable) {
"A" -> AsmFragment(" lsr a | bcc + | ora #\$80 |+", 10)
"X" -> AsmFragment(" txa | lsr a | bcc + | ora #\$80 |+ | tax", 10)
"Y" -> AsmFragment(" tya | lsr a | bcc + | ora #\$80 |+ | tay", 10)
else -> AsmFragment(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable", 10)
}
}
Opcode.ROL2_WORD -> {
AsmFragment(" lda $variable | cmp #\$80 | rol $variable | rol $variable+1", 10)
}
Opcode.ROR2_WORD -> {
AsmFragment(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+", 30)
}
else -> null
}
}
private class AsmFragment(val asm: String, var segmentSize: Int=0)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,562 +0,0 @@
package oldcodegen
/** OLD STACK-VM CODE GEN -- NO LONGER USED **/
import prog8.compiler.CompilerException
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS2_HEX
import prog8.compiler.toHex
import prog8.vm.stackvm.Syscall
import prog8.vm.stackvm.syscallsForStackVm
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
private var breakpointCounter = 0
internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.ProgramBlock): String? {
// a label 'instruction' is simply translated into a asm label
if(ins is LabelInstr) {
val labelresult =
if(ins.name.startsWith("${block.name}."))
ins.name.substring(block.name.length+1)
else
ins.name
return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult
}
// simple opcodes that are translated directly into one or a few asm instructions
return when(ins.opcode) {
Opcode.LINE -> " ;\tsrc line: ${ins.callLabel}"
Opcode.NOP -> " nop" // shouldn't be present anymore though
Opcode.START_PROCDEF -> "" // is done as part of a label
Opcode.END_PROCDEF -> " .pend"
Opcode.TERMINATE -> " brk"
Opcode.SEC -> " sec"
Opcode.CLC -> " clc"
Opcode.SEI -> " sei"
Opcode.CLI -> " cli"
Opcode.CARRY_TO_A -> " lda #0 | adc #0"
Opcode.JUMP -> {
if(ins.callLabel!=null)
" jmp ${ins.callLabel}"
else
" jmp ${hexVal(ins)}"
}
Opcode.CALL -> {
if(ins.callLabel!=null)
" jsr ${ins.callLabel}"
else
" jsr ${hexVal(ins)}"
}
Opcode.RETURN -> " rts"
Opcode.RSAVE -> {
// save cpu status flag and all registers A, X, Y.
// see http://6502.org/tutorials/register_preservation.html
" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}"
}
Opcode.RRESTORE -> {
// restore all registers and cpu status flag
" pla | tay | pla | tax | pla | plp"
}
Opcode.RSAVEX -> " sta ${C64Zeropage.SCRATCH_REG} | txa | pha | lda ${C64Zeropage.SCRATCH_REG}"
Opcode.RRESTOREX -> " sta ${C64Zeropage.SCRATCH_REG} | pla | tax | lda ${C64Zeropage.SCRATCH_REG}"
Opcode.DISCARD_BYTE -> " inx"
Opcode.DISCARD_WORD -> " inx"
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.DUP_B -> {
" lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | dex | ;DUP_B "
}
Opcode.DUP_W -> {
" lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_HI_HEX,x | dex "
}
Opcode.CMP_B, Opcode.CMP_UB -> {
" inx | lda $ESTACK_LO_HEX,x | cmp #${ins.arg!!.integerValue().toHex()} | ;CMP_B "
}
Opcode.CMP_W, Opcode.CMP_UW -> {
"""
inx
lda $ESTACK_HI_HEX,x
cmp #>${ins.arg!!.integerValue().toHex()}
bne +
lda $ESTACK_LO_HEX,x
cmp #<${ins.arg.integerValue().toHex()}
; bne + not necessary?
; lda #0 not necessary?
+
"""
}
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to accept it.
Opcode.INCLUDE_FILE -> {
val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}"
val length = if(ins.arg2==null) "" else ", ${ins.arg2.integerValue()}"
" .binary \"${ins.callLabel}\" $offset $length"
}
Opcode.SYSCALL -> {
if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr })
throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}")
val call = Syscall.values().find { it.callNr==ins.arg.numericValue() }
when(call) {
Syscall.FUNC_SIN,
Syscall.FUNC_COS,
Syscall.FUNC_ABS,
Syscall.FUNC_TAN,
Syscall.FUNC_ATAN,
Syscall.FUNC_LN,
Syscall.FUNC_LOG2,
Syscall.FUNC_SQRT,
Syscall.FUNC_RAD,
Syscall.FUNC_DEG,
Syscall.FUNC_ROUND,
Syscall.FUNC_FLOOR,
Syscall.FUNC_CEIL,
Syscall.FUNC_RNDF,
Syscall.FUNC_ANY_F,
Syscall.FUNC_ALL_F,
Syscall.FUNC_MAX_F,
Syscall.FUNC_MIN_F,
Syscall.FUNC_SUM_F -> " jsr c64flt.${call.name.toLowerCase()}"
null -> ""
else -> " jsr prog8_lib.${call.name.toLowerCase()}"
}
}
Opcode.BREAKPOINT -> {
breakpointCounter++
"_prog8_breakpoint_$breakpointCounter\tnop"
}
Opcode.PUSH_BYTE -> {
" lda #${hexVal(ins)} | sta $ESTACK_LO_HEX,x | dex"
}
Opcode.PUSH_WORD -> {
val value = hexVal(ins)
" lda #<$value | sta $ESTACK_LO_HEX,x | lda #>$value | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.PUSH_FLOAT -> {
val floatConst = getFloatConst(ins.arg!!)
" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float"
}
Opcode.PUSH_VAR_BYTE -> {
when(ins.callLabel) {
"X" -> throw CompilerException("makes no sense to push X, it's used as a stack pointer itself. You should probably not use the X register (or only in trivial assignments)")
"A" -> " sta $ESTACK_LO_HEX,x | dex"
"Y" -> " tya | sta $ESTACK_LO_HEX,x | dex"
else -> " lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | dex"
}
}
Opcode.PUSH_VAR_WORD -> {
" lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda ${ins.callLabel}+1 | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.PUSH_VAR_FLOAT -> " lda #<${ins.callLabel} | ldy #>${ins.callLabel}| jsr c64flt.push_float"
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> {
"""
lda ${hexVal(ins)}
sta $ESTACK_LO_HEX,x
dex
"""
}
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> {
"""
lda ${hexVal(ins)}
sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(ins)}
sta $ESTACK_HI_HEX,x
dex
"""
}
Opcode.PUSH_MEM_FLOAT -> {
" lda #<${hexVal(ins)} | ldy #>${hexVal(ins)}| jsr c64flt.push_float"
}
Opcode.PUSH_MEMREAD -> {
"""
lda $ESTACK_LO_PLUS1_HEX,x
sta (+) +1
lda $ESTACK_HI_PLUS1_HEX,x
sta (+) +2
+ lda 65535 ; modified
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.PUSH_REGAY_WORD -> {
" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex "
}
Opcode.PUSH_ADDR_HEAPVAR -> {
" lda #<${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda #>${ins.callLabel} | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGAY_WORD -> {
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x "
}
Opcode.READ_INDEXED_VAR_BYTE -> {
"""
ldy $ESTACK_LO_PLUS1_HEX,x
lda ${ins.callLabel},y
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.READ_INDEXED_VAR_WORD -> {
"""
lda $ESTACK_LO_PLUS1_HEX,x
asl a
tay
lda ${ins.callLabel},y
sta $ESTACK_LO_PLUS1_HEX,x
lda ${ins.callLabel}+1,y
sta $ESTACK_HI_PLUS1_HEX,x
"""
}
Opcode.READ_INDEXED_VAR_FLOAT -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.push_float_from_indexed_var
"""
}
Opcode.WRITE_INDEXED_VAR_BYTE -> {
"""
inx
ldy $ESTACK_LO_HEX,x
inx
lda $ESTACK_LO_HEX,x
sta ${ins.callLabel},y
"""
}
Opcode.WRITE_INDEXED_VAR_WORD -> {
"""
inx
lda $ESTACK_LO_HEX,x
asl a
tay
inx
lda $ESTACK_LO_HEX,x
sta ${ins.callLabel},y
lda $ESTACK_HI_HEX,x
sta ${ins.callLabel}+1,y
"""
}
Opcode.WRITE_INDEXED_VAR_FLOAT -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.pop_float_to_indexed_var
"""
}
Opcode.POP_MEM_BYTE -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta ${hexVal(ins)}
"""
}
Opcode.POP_MEM_WORD -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta ${hexVal(ins)}
lda $ESTACK_HI_HEX,x
sta ${hexValPlusOne(ins)}
"""
}
Opcode.POP_MEM_FLOAT -> {
" lda ${hexVal(ins)} | ldy ${hexValPlusOne(ins)} | jsr c64flt.pop_float"
}
Opcode.POP_MEMWRITE -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
inx
lda $ESTACK_LO_HEX,x
+ sta 65535 ; modified
"""
}
Opcode.POP_VAR_BYTE -> {
when (ins.callLabel) {
"X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself")
"A" -> " inx | lda $ESTACK_LO_HEX,x"
"Y" -> " inx | ldy $ESTACK_LO_HEX,x"
else -> " inx | lda $ESTACK_LO_HEX,x | sta ${ins.callLabel}"
}
}
Opcode.POP_VAR_WORD -> {
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x | sta ${ins.callLabel} | sty ${ins.callLabel}+1"
}
Opcode.POP_VAR_FLOAT -> {
" lda #<${ins.callLabel} | ldy #>${ins.callLabel} | jsr c64flt.pop_float"
}
Opcode.INC_VAR_UB, Opcode.INC_VAR_B -> {
when (ins.callLabel) {
"A" -> " clc | adc #1"
"X" -> " inx"
"Y" -> " iny"
else -> " inc ${ins.callLabel}"
}
}
Opcode.INC_VAR_UW, Opcode.INC_VAR_W -> {
" inc ${ins.callLabel} | bne + | inc ${ins.callLabel}+1 |+"
}
Opcode.INC_VAR_F -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.inc_var_f
"""
}
Opcode.POP_INC_MEMORY -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
+ inc 65535 ; modified
"""
}
Opcode.POP_DEC_MEMORY -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
+ dec 65535 ; modified
"""
}
Opcode.DEC_VAR_UB, Opcode.DEC_VAR_B -> {
when (ins.callLabel) {
"A" -> " sec | sbc #1"
"X" -> " dex"
"Y" -> " dey"
else -> " dec ${ins.callLabel}"
}
}
Opcode.DEC_VAR_UW, Opcode.DEC_VAR_W -> {
" lda ${ins.callLabel} | bne + | dec ${ins.callLabel}+1 |+ | dec ${ins.callLabel}"
}
Opcode.DEC_VAR_F -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.dec_var_f
"""
}
Opcode.INC_MEMORY -> " inc ${hexVal(ins)}"
Opcode.DEC_MEMORY -> " dec ${hexVal(ins)}"
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | inc ${ins.callLabel},x | pla | tax"
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | dec ${ins.callLabel},x | pla | tax"
Opcode.NEG_B -> " jsr prog8_lib.neg_b"
Opcode.NEG_W -> " jsr prog8_lib.neg_w"
Opcode.NEG_F -> " jsr c64flt.neg_f"
Opcode.ABS_B -> " jsr prog8_lib.abs_b"
Opcode.ABS_W -> " jsr prog8_lib.abs_w"
Opcode.ABS_F -> " jsr c64flt.abs_f"
Opcode.POW_F -> " jsr c64flt.pow_f"
Opcode.INV_BYTE -> {
"""
lda $ESTACK_LO_PLUS1_HEX,x
eor #255
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.INV_WORD -> " jsr prog8_lib.inv_word"
Opcode.NOT_BYTE -> " jsr prog8_lib.not_byte"
Opcode.NOT_WORD -> " jsr prog8_lib.not_word"
Opcode.BCS -> {
val label = ins.callLabel ?: hexVal(ins)
" bcs $label"
}
Opcode.BCC -> {
val label = ins.callLabel ?: hexVal(ins)
" bcc $label"
}
Opcode.BNEG -> {
val label = ins.callLabel ?: hexVal(ins)
" bmi $label"
}
Opcode.BPOS -> {
val label = ins.callLabel ?: hexVal(ins)
" bpl $label"
}
Opcode.BVC -> {
val label = ins.callLabel ?: hexVal(ins)
" bvc $label"
}
Opcode.BVS -> {
val label = ins.callLabel ?: hexVal(ins)
" bvs $label"
}
Opcode.BZ -> {
val label = ins.callLabel ?: hexVal(ins)
" beq $label"
}
Opcode.BNZ -> {
val label = ins.callLabel ?: hexVal(ins)
" bne $label"
}
Opcode.JZ -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
beq $label
"""
}
Opcode.JZW -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
beq $label
lda $ESTACK_HI_HEX,x
beq $label
"""
}
Opcode.JNZ -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
bne $label
"""
}
Opcode.JNZW -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
bne $label
lda $ESTACK_HI_HEX,x
bne $label
"""
}
Opcode.CAST_B_TO_UB -> "" // is a no-op, just carry on with the byte as-is
Opcode.CAST_UB_TO_B -> "" // is a no-op, just carry on with the byte as-is
Opcode.CAST_W_TO_UW -> "" // is a no-op, just carry on with the word as-is
Opcode.CAST_UW_TO_W -> "" // is a no-op, just carry on with the word as-is
Opcode.CAST_W_TO_UB -> "" // is a no-op, just carry on with the lsb of the word as-is
Opcode.CAST_W_TO_B -> "" // is a no-op, just carry on with the lsb of the word as-is
Opcode.CAST_UW_TO_UB -> "" // is a no-op, just carry on with the lsb of the uword as-is
Opcode.CAST_UW_TO_B -> "" // is a no-op, just carry on with the lsb of the uword as-is
Opcode.CAST_UB_TO_F -> " jsr c64flt.stack_ub2float"
Opcode.CAST_B_TO_F -> " jsr c64flt.stack_b2float"
Opcode.CAST_UW_TO_F -> " jsr c64flt.stack_uw2float"
Opcode.CAST_W_TO_F -> " jsr c64flt.stack_w2float"
Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2ub"
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2b"
Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw"
Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w"
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta $ESTACK_HI_PLUS1_HEX,x" // clear the msb
Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda $ESTACK_LO_PLUS1_HEX,x | ${signExtendA("$ESTACK_HI_PLUS1_HEX,x")}" // sign extend the lsb
Opcode.MSB -> " lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x"
Opcode.MKWORD -> " inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x "
Opcode.ADD_UB, Opcode.ADD_B -> { // TODO inline better (pattern with more opcodes)
"""
lda $ESTACK_LO_PLUS2_HEX,x
clc
adc $ESTACK_LO_PLUS1_HEX,x
inx
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.SUB_UB, Opcode.SUB_B -> { // TODO inline better (pattern with more opcodes)
"""
lda $ESTACK_LO_PLUS2_HEX,x
sec
sbc $ESTACK_LO_PLUS1_HEX,x
inx
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.ADD_W, Opcode.ADD_UW -> " jsr prog8_lib.add_w"
Opcode.SUB_W, Opcode.SUB_UW -> " jsr prog8_lib.sub_w"
Opcode.MUL_B, Opcode.MUL_UB -> " jsr prog8_lib.mul_byte"
Opcode.MUL_W, Opcode.MUL_UW -> " jsr prog8_lib.mul_word"
Opcode.MUL_F -> " jsr c64flt.mul_f"
Opcode.ADD_F -> " jsr c64flt.add_f"
Opcode.SUB_F -> " jsr c64flt.sub_f"
Opcode.DIV_F -> " jsr c64flt.div_f"
Opcode.IDIV_UB -> " jsr prog8_lib.idiv_ub"
Opcode.IDIV_B -> " jsr prog8_lib.idiv_b"
Opcode.IDIV_W -> " jsr prog8_lib.idiv_w"
Opcode.IDIV_UW -> " jsr prog8_lib.idiv_uw"
Opcode.AND_BYTE -> " jsr prog8_lib.and_b"
Opcode.OR_BYTE -> " jsr prog8_lib.or_b"
Opcode.XOR_BYTE -> " jsr prog8_lib.xor_b"
Opcode.AND_WORD -> " jsr prog8_lib.and_w"
Opcode.OR_WORD -> " jsr prog8_lib.or_w"
Opcode.XOR_WORD -> " jsr prog8_lib.xor_w"
Opcode.BITAND_BYTE -> " jsr prog8_lib.bitand_b"
Opcode.BITOR_BYTE -> " jsr prog8_lib.bitor_b"
Opcode.BITXOR_BYTE -> " jsr prog8_lib.bitxor_b"
Opcode.BITAND_WORD -> " jsr prog8_lib.bitand_w"
Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
Opcode.GREATER_B -> " jsr prog8_lib.greater_b"
Opcode.GREATER_UB -> " jsr prog8_lib.greater_ub"
Opcode.GREATER_W -> " jsr prog8_lib.greater_w"
Opcode.GREATER_UW -> " jsr prog8_lib.greater_uw"
Opcode.GREATER_F -> " jsr c64flt.greater_f"
Opcode.GREATEREQ_B -> " jsr prog8_lib.greatereq_b"
Opcode.GREATEREQ_UB -> " jsr prog8_lib.greatereq_ub"
Opcode.GREATEREQ_W -> " jsr prog8_lib.greatereq_w"
Opcode.GREATEREQ_UW -> " jsr prog8_lib.greatereq_uw"
Opcode.GREATEREQ_F -> " jsr c64flt.greatereq_f"
Opcode.EQUAL_BYTE -> " jsr prog8_lib.equal_b"
Opcode.EQUAL_WORD -> " jsr prog8_lib.equal_w"
Opcode.EQUAL_F -> " jsr c64flt.equal_f"
Opcode.NOTEQUAL_BYTE -> " jsr prog8_lib.notequal_b"
Opcode.NOTEQUAL_WORD -> " jsr prog8_lib.notequal_w"
Opcode.NOTEQUAL_F -> " jsr c64flt.notequal_f"
Opcode.LESS_UB -> " jsr prog8_lib.less_ub"
Opcode.LESS_B -> " jsr prog8_lib.less_b"
Opcode.LESS_UW -> " jsr prog8_lib.less_uw"
Opcode.LESS_W -> " jsr prog8_lib.less_w"
Opcode.LESS_F -> " jsr c64flt.less_f"
Opcode.LESSEQ_UB -> " jsr prog8_lib.lesseq_ub"
Opcode.LESSEQ_B -> " jsr prog8_lib.lesseq_b"
Opcode.LESSEQ_UW -> " jsr prog8_lib.lesseq_uw"
Opcode.LESSEQ_W -> " jsr prog8_lib.lesseq_w"
Opcode.LESSEQ_F -> " jsr c64flt.lesseq_f"
Opcode.SHIFTEDL_BYTE -> " asl $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDL_WORD -> " asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x"
Opcode.SHIFTEDR_SBYTE -> " lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_UBYTE -> " lsr $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_SWORD -> " lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_UWORD -> " lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
else -> null
}
}

113
README.md
View File

@ -1,64 +1,73 @@
[![saythanks](https://img.shields.io/badge/say-thanks-ff69b4.svg)](https://saythanks.io/to/irmen)
[![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/)
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
===========================================================================
Prog8 - Structured Programming Language for 8-bit 6502/65c02 microprocessors
============================================================================
*Written by Irmen de Jong (irmen@razorvine.net)*
*Software license: GNU GPL 3.0, see file LICENSE*
This is a structured programming language for the 8-bit 6502/6510 microprocessor from the late 1970's and 1980's
This is a structured programming language for the 8-bit 6502/6510/65c02 microprocessor from the late 1970's and 1980's
as used in many home computers from that era. It is a medium to low level programming language,
which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
which aims to provide many conveniences over raw assembly code (even when using a macro assembler).
- reduction of source code length
Documentation
-------------
Full documentation (syntax reference, how to use the language and the compiler, etc.) can be found at:
https://prog8.readthedocs.io/
What does Prog8 provide?
------------------------
- big reduction of source code length over raw assembly
- modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string and array variables and string sharing
- subroutines with a input- and output parameter signature
- constant folding in expressions
- subroutines with an input- and output parameter signature
- no stack frame allocations because parameters and local variables are automatically allocated statically
- constant folding in expressions and other high-level program optimizations
- conditional branches
- 'when' statement to provide a concise jump table alternative to if/elseif chains
- structs to group together sets of variables and manipulate them at once
- floating point operations (requires the C64 Basic ROM routines for this)
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
- inline assembly allows you to have full control when every cycle or byte matters
- 'when' statement to provide a concise jump table alternative to if/elseif chains
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
- structs to group together sets of variables and manipulate them at once
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- fast execution speed due to compilation to native assembly code
- inline assembly allows you to have full control when every cycle or byte matters
Rapid edit-compile-run-debug cycle:
*Rapid edit-compile-run-debug cycle:*
- use modern PC to work on
- quick compilation times (seconds)
- option to automatically run the program in the Vice emulator
- use a modern PC to do the work on, use nice editors and enjoy quick compilation times
- can automatically run the program in the Vice emulator after succesful compilation
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
- virtual machine that can execute compiled code directy on the host system,
without having to actually convert it to assembly to run on a real 6502
It is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome.
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
Documentation/manual
--------------------
See https://prog8.readthedocs.io/
- "c64": Commodore-64 (6510 CPU = almost a 6502), the main target.
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) .
- 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.
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
For other platforms it is very easy to compile it yourself (make ; make install).
A **Java runtime (jre or jdk), version 8 or newer** is required to run a prepackaged version of the compiler.
A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
IntelliJ IDEA with the Kotlin plugin).
It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence
of the [Vice emulator](http://vice-emu.sourceforge.net/)
It's handy to have an emulator (or a real machine perhaps!) to run the programs on. The compiler assumes the presence
of the [Vice emulator](http://vice-emu.sourceforge.net/) for the C64 target,
and the [x16emu emulator](https://github.com/commanderx16/x16-emulator) for the CommanderX16 target.
Example code
@ -66,44 +75,45 @@ Example code
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64utils
%import textio
%zeropage basicsafe
main {
ubyte[256] sieve
ubyte candidate_prime = 2
ubyte candidate_prime = 2 ; is increased in the loop
sub start() {
memset(sieve, 256, false)
c64scr.print("prime numbers up to 255:\n\n")
; clear the sieve, to reset starting situation on subsequent runs
memset(sieve, 256, false)
; calculate primes
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0
while true {
repeat {
ubyte prime = find_next_prime()
if prime==0
break
c64scr.print_ub(prime)
c64scr.print(", ")
txt.print_ub(prime)
txt.print(", ")
amount++
}
c64.CHROUT('\n')
c64scr.print("number of primes (expected 54): ")
c64scr.print_ub(amount)
c64.CHROUT('\n')
txt.chrout('\n')
txt.print("number of primes (expected 54): ")
txt.print_ub(amount)
txt.chrout('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0
return 0 ; we wrapped; no more primes available in the sieve
}
; found next one, mark the multiples and return it.
sieve[candidate_prime] = true
uword multiple = candidate_prime
while multiple < len(sieve) {
sieve[lsb(multiple)] = true
multiple += candidate_prime
@ -113,11 +123,11 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
}
when compiled an ran on a C-64 you'll get:
![c64 screen](docs/source/_static/primes_example.png)
One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this:
![wizzine screen](docs/source/_static/wizzine.png)
@ -129,3 +139,8 @@ Another example (cube3d-sprites.p8) draws the vertices of a rotating 3d cube:
If you want to play a video game, a fully working Tetris clone is included in the examples:
![tehtriz_screen](docs/source/_static/tehtriz.png)
The CommanderX16 compiler target is quite capable already too, here's a well known space ship
animated in 3D with hidden line removal, in the CommanderX16 emulator:
![cobra3d](docs/source/_static/cobra3d.png)

View File

@ -1,22 +1,22 @@
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20"
}
}
plugins {
// id "org.jetbrains.kotlin.jvm" version $kotlinVersion
// id "org.jetbrains.kotlin.jvm" version "1.4.20"
id 'application'
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.1.0'
id 'com.github.johnrengelman.shadow' version '5.2.0'
id 'java'
}
apply plugin: "kotlin"
apply plugin: "java"
targetCompatibility = 1.8
sourceCompatibility = 1.8
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenLocal()
@ -29,22 +29,23 @@ def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':parser')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
// implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
// runtime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
runtime 'org.antlr:antlr4-runtime:4.7.2'
runtime project(':parser')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.antlr:antlr4-runtime:4.8'
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
// implementation 'net.razorvine:ksim65:1.6'
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
implementation project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
compile 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
@ -52,7 +53,7 @@ compileKotlin {
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "11"
}
}
@ -85,8 +86,8 @@ artifacts {
shadowJar {
baseName = 'prog8compiler'
version = prog8version
archiveBaseName = 'prog8compiler'
archiveVersion = prog8version
// minimize()
}
@ -100,7 +101,7 @@ test {
// Show test results.
testLogging {
events "passed", "skipped", "failed"
events "skipped", "failed"
}
}
@ -109,3 +110,7 @@ dokka {
outputFormat = 'html'
outputDirectory = "$buildDir/kdoc"
}
task wrapper(type: Wrapper) {
gradleVersion = '6.1.1'
}

View File

@ -8,12 +8,12 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
<orderEntry type="library" name="antlr-runtime-4.8" level="project" />
</component>
</module>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,676 @@
; --- low level floating point assembly routines for the C64
FL_ONE_const .byte 129 ; 1.0
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
floats_store_reg .byte 0 ; temp storage
ub2float .proc
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy P8ZP_SCRATCH_B1
lda #0
jsr GIVAYF
_fac_to_mem ldx P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
jsr MOVMF
ldx P8ZP_SCRATCH_REG
rts
.pend
b2float .proc
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_B1
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
uw2float .proc
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
w2float .proc
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
stx P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W1+1
jsr GIVAYF
jmp ub2float._fac_to_mem
.pend
cast_from_uw .proc
; -- uword in A/Y into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
cast_from_w .proc
; -- word in A/Y into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr GIVAYFAY
jmp ub2float._fac_to_mem
.pend
cast_from_ub .proc
; -- ubyte in Y into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr FREADUY
jmp ub2float._fac_to_mem
.pend
cast_from_b .proc
; -- byte in A into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
cast_as_uw_into_ya .proc ; also used for float 2 ub
; -- cast float at A/Y to uword into Y/A
jsr MOVFM
jmp cast_FAC1_as_uw_into_ya
.pend
cast_as_w_into_ay .proc ; also used for float 2 b
; -- cast float at A/Y to word into A/Y
jsr MOVFM
jmp cast_FAC1_as_w_into_ay
.pend
cast_FAC1_as_uw_into_ya .proc ; also used for float 2 ub
; -- cast fac1 to uword into Y/A
stx P8ZP_SCRATCH_REG
jsr GETADR ; into Y/A
ldx P8ZP_SCRATCH_REG
rts
.pend
cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
; -- cast fac1 to word into A/Y
stx P8ZP_SCRATCH_REG
jsr AYINT
ldy $64
lda $65
ldx P8ZP_SCRATCH_REG
rts
.pend
stack_b2float .proc
; -- b2float operating on the stack
inx
lda P8ESTACK_LO,x
stx P8ZP_SCRATCH_REG
jsr FREADSA
jmp push_fac1._internal
.pend
stack_w2float .proc
; -- w2float operating on the stack
inx
ldy P8ESTACK_LO,x
lda P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG
jsr GIVAYF
jmp push_fac1._internal
.pend
stack_ub2float .proc
; -- ub2float operating on the stack
inx
lda P8ESTACK_LO,x
stx P8ZP_SCRATCH_REG
tay
lda #0
jsr GIVAYF
jmp push_fac1._internal
.pend
stack_uw2float .proc
; -- uw2float operating on the stack
inx
lda P8ESTACK_LO,x
ldy P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY
jmp push_fac1._internal
.pend
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr AYINT
ldx P8ZP_SCRATCH_REG
lda $64
sta P8ESTACK_HI,x
lda $65
sta P8ESTACK_LO,x
dex
rts
.pend
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr GETADR
ldx P8ZP_SCRATCH_REG
sta P8ESTACK_HI,x
tya
sta P8ESTACK_LO,x
dex
rts
.pend
push_float .proc
; ---- push mflpt5 in A/Y onto stack
; (taking 3 stack positions = 6 bytes of which 1 is padding)
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_LO,x
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_HI,x
dex
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_LO,x
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_HI,x
dex
iny
lda (P8ZP_SCRATCH_W1),y
sta P8ESTACK_LO,x
dex
rts
.pend
pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding)
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #4
inx
lda P8ESTACK_LO,x
sta (P8ZP_SCRATCH_W1),y
dey
inx
lda P8ESTACK_HI,x
sta (P8ZP_SCRATCH_W1),y
dey
lda P8ESTACK_LO,x
sta (P8ZP_SCRATCH_W1),y
dey
inx
lda P8ESTACK_HI,x
sta (P8ZP_SCRATCH_W1),y
dey
lda P8ESTACK_LO,x
sta (P8ZP_SCRATCH_W1),y
rts
.pend
pop_float_fac1 .proc
; -- pops float from stack into FAC1
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp MOVFM
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta _target+1
sty _target+2
ldy #4
_loop lda (P8ZP_SCRATCH_W1),y
_target sta $ffff,y ; modified
dey
bpl _loop
rts
.pend
inc_var_f .proc
; -- add 1 to float pointed to by A/Y
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_REG
jsr MOVFM
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr FADD
ldx P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr MOVMF
ldx P8ZP_SCRATCH_REG
rts
.pend
dec_var_f .proc
; -- subtract 1 from float pointed to by A/Y
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
stx P8ZP_SCRATCH_REG
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr MOVFM
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr FSUB
ldx P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr MOVMF
ldx P8ZP_SCRATCH_REG
rts
.pend
pop_2_floats_f2_in_fac1 .proc
; -- pop 2 floats from stack, load the second one in FAC1 as well
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float2
ldy #>fmath_float2
jmp MOVFM
.pend
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
push_fac1 .proc
; -- push the float in FAC1 onto the stack
stx P8ZP_SCRATCH_REG
_internal ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
lda #<fmath_float1
ldy #>fmath_float1
ldx P8ZP_SCRATCH_REG
jmp push_float
.pend
pow_f .proc
; -- push f1 ** f2 on stack
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr CONUPK ; fac2 = float1
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
jmp push_fac1._internal
.pend
div_f .proc
; -- push f1/f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FDIV
jmp push_fac1._internal
.pend
add_f .proc
; -- push f1+f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FADD
jmp push_fac1._internal
.pend
sub_f .proc
; -- push f1-f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FSUB
jmp push_fac1._internal
.pend
mul_f .proc
; -- push f1*f2 on stack
jsr pop_2_floats_f2_in_fac1
stx P8ZP_SCRATCH_REG
lda #<fmath_float1
ldy #>fmath_float1
jsr FMULT
jmp push_fac1._internal
.pend
neg_f .proc
; -- toggle the sign bit on the stack
lda P8ESTACK_HI+3,x
eor #$80
sta P8ESTACK_HI+3,x
rts
.pend
var_fac1_less_f .proc
; -- is the float in FAC1 < the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
beq +
lda #0
rts
+ lda #1
rts
.pend
var_fac1_lesseq_f .proc
; -- is the float in FAC1 <= the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #0
beq +
cmp #255
beq +
lda #0
rts
+ lda #1
rts
.pend
var_fac1_greater_f .proc
; -- is the float in FAC1 > the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #1
beq +
lda #0
+ rts
.pend
var_fac1_greatereq_f .proc
; -- is the float in FAC1 >= the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #0
beq +
cmp #1
beq +
lda #0
rts
+ lda #1
rts
.pend
var_fac1_notequal_f .proc
; -- are the floats numbers in FAC1 and the variable AY *not* identical?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
and #1
rts
.pend
vars_equal_f .proc
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical?
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
lda #1
rts
_false lda #0
rts
.pend
equal_f .proc
; -- are the two mflpt5 numbers on the stack identical?
inx
inx
inx
inx
lda P8ESTACK_LO-3,x
cmp P8ESTACK_LO,x
bne _equals_false
lda P8ESTACK_LO-2,x
cmp P8ESTACK_LO+1,x
bne _equals_false
lda P8ESTACK_LO-1,x
cmp P8ESTACK_LO+2,x
bne _equals_false
lda P8ESTACK_HI-2,x
cmp P8ESTACK_HI+1,x
bne _equals_false
lda P8ESTACK_HI-1,x
cmp P8ESTACK_HI+2,x
bne _equals_false
_equals_true lda #1
_equals_store inx
sta P8ESTACK_LO+1,x
rts
_equals_false lda #0
beq _equals_store
.pend
notequal_f .proc
; -- are the two mflpt5 numbers on the stack different?
jsr equal_f
eor #1 ; invert the result
sta P8ESTACK_LO+1,x
rts
.pend
vars_less_f .proc
; -- is float in AY < float in P8ZP_SCRATCH_W2 ?
jsr MOVFM
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
bne +
lda #1
rts
+ lda #0
rts
.pend
vars_lesseq_f .proc
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ?
jsr MOVFM
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
bne +
- lda #1
rts
+ cmp #0
beq -
lda #0
rts
.pend
less_f .proc
; -- is f1 < f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
bne compare_floats._return_false
.pend
lesseq_f .proc
; -- is f1 <= f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greater_f .proc
; -- is f1 > f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greatereq_f .proc
; -- is f1 >= f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
compare_floats .proc
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr MOVFM ; fac1 = flt1
lda #<fmath_float2
ldy #>fmath_float2
stx P8ZP_SCRATCH_REG
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
ldx P8ZP_SCRATCH_REG
rts
_return_false lda #0
_return_result sta P8ESTACK_LO,x
dex
rts
_return_true lda #1
bne _return_result
.pend
set_array_float_from_fac1 .proc
; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1
ldy P8ZP_SCRATCH_W1+1
clc
adc P8ZP_SCRATCH_W1
bcc +
iny
+ stx floats_store_reg
tax
jsr MOVMF
ldx floats_store_reg
rts
.pend
set_0_array_float .proc
; -- set a float in an array to zero (index in A, array in P8ZP_SCRATCH_W1)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1
tay
lda #0
sta (P8ZP_SCRATCH_W1),y
iny
sta (P8ZP_SCRATCH_W1),y
iny
sta (P8ZP_SCRATCH_W1),y
iny
sta (P8ZP_SCRATCH_W1),y
iny
sta (P8ZP_SCRATCH_W1),y
rts
.pend
set_array_float .proc
; -- set a float in an array to a value (index in A, float in P8ZP_SCRATCH_W1, array in P8ZP_SCRATCH_W2)
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1
adc P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
bcc +
iny
+ jmp copy_float
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
.pend

View File

@ -0,0 +1,218 @@
; Prog8 definitions for floating point handling on the Commodore-64
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
%target c64
%option enable_floats
floats {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
; ---- C64 basic and kernal ROM float constants and 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.
; constants in five-byte "mflpt" format in the BASIC ROM
&float FL_PIVAL = $aea8 ; 3.1415926...
&float FL_N32768 = $b1a5 ; -32768
&float FL_FONE = $b9bc ; 1
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
&float FL_SQRTWO = $b9db ; SQR(2)
&float FL_NEGHLF = $b9e0 ; -.5
&float FL_LOG2 = $b9e5 ; LOG(2)
&float FL_TENC = $baf9 ; 10
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
&float FL_FHALF = $bf11 ; .5
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
&float FL_PIHALF = $e2e0 ; PI / 2
&float FL_TWOPI = $e2e5 ; 2 * PI
&float FL_FR4 = $e2ea ; .25
; oddly enough, 0.0 isn't available in the kernel.
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
romsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
romsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; (tip: use floats.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; there is also floats.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also floats.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $bc5b = 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 $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
romsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
romsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
romsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
romsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $bc58 = ABS() ; fac1 = ABS(fac1)
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $e2b4 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $e30e = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
asmsub FREADS32() clobbers(A,X,Y) {
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
%asm {{
lda $62
eor #$ff
asl a
lda #0
ldx #$a0
jmp $bc4f ; internal BASIC routine
}}
}
asmsub FREADUS32 () clobbers(A,X,Y) {
; ---- fac1 = uint32 from $62-$65 big endian (MSB FIRST)
%asm {{
sec
lda #0
ldx #$a0
jmp $bc4f ; internal BASIC routine
}}
}
asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y) {
; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes)
; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead.
%asm {{
sty $62
stx $63
sta $64
lda $62
eor #$FF
asl a
lda #0
sta $65
ldx #$98
jmp $bc4f ; internal BASIC routine
}}
}
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
sty $62
sta $63
ldx #$90
sec
jmp $bc49 ; internal BASIC routine
}}
}
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{
sta P8ZP_SCRATCH_REG
tya
ldy P8ZP_SCRATCH_REG
jmp GIVAYF ; this uses the inverse order, Y/A
}}
}
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y
%asm {{
jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_REG
tya
ldy P8ZP_SCRATCH_REG
rts
}}
}
asmsub GETADRAY () clobbers(X) -> uword @ AY {
; ---- fac1 to unsigned word in A/Y
%asm {{
jsr GETADR ; this uses the inverse order, Y/A
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_B1
rts
}}
}
sub print_f (float value) {
; ---- prints the floating point value (without a newline).
%asm {{
stx floats_store_reg
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 -
+ ldx floats_store_reg
rts
}}
}
%asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
}

View File

@ -0,0 +1,437 @@
; --- floating point builtin functions
abs_f_stack .proc
; -- push abs(AY) on stack
jsr floats.MOVFM
jsr floats.ABS
jmp push_fac1
.pend
abs_f_fac1 .proc
; -- FAC1 = abs(AY)
jsr floats.MOVFM
jmp floats.ABS
.pend
func_atan_stack .proc
jsr func_atan_fac1
jmp push_fac1
.pend
func_atan_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr ATN
ldx P8ZP_SCRATCH_REG
rts
.pend
func_ceil_stack .proc
jsr func_ceil_fac1
jmp push_fac1
.pend
func_ceil_fac1 .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr MOVFM
stx P8ZP_SCRATCH_REG
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1
ldy #>fmath_float1
jsr FCOMP
cmp #0
beq +
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr FADD
+ ldx P8ZP_SCRATCH_REG
rts
.pend
func_floor_stack .proc
jsr func_floor_fac1
jmp push_fac1
.pend
func_floor_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr INT
ldx P8ZP_SCRATCH_REG
rts
.pend
func_round_stack .proc
jsr func_round_fac1
jmp push_fac1
.pend
func_round_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr FADDH
jsr INT
ldx P8ZP_SCRATCH_REG
rts
.pend
func_sin_stack .proc
jsr func_sin_fac1
jmp push_fac1
.pend
func_sin_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr SIN
ldx P8ZP_SCRATCH_REG
rts
.pend
func_cos_stack .proc
jsr func_cos_fac1
jmp push_fac1
.pend
func_cos_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr COS
ldx P8ZP_SCRATCH_REG
rts
.pend
func_tan_stack .proc
jsr func_tan_fac1
jmp push_fac1
.pend
func_tan_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr TAN
ldx P8ZP_SCRATCH_REG
rts
.pend
func_rad_stack .proc
jsr func_rad_fac1
jmp push_fac1
.pend
func_rad_fac1 .proc
; -- convert degrees to radians (d * pi / 180)
jsr MOVFM
stx P8ZP_SCRATCH_REG
lda #<_pi_div_180
ldy #>_pi_div_180
jsr FMULT
ldx P8ZP_SCRATCH_REG
rts
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend
func_deg_stack .proc
jsr func_deg_fac1
jmp push_fac1
.pend
func_deg_fac1 .proc
; -- convert radians to degrees (d * (1/ pi * 180))
jsr MOVFM
stx P8ZP_SCRATCH_REG
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr FMULT
ldx P8ZP_SCRATCH_REG
rts
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend
func_ln_stack .proc
jsr func_ln_fac1
jmp push_fac1
.pend
func_ln_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr LOG
ldx P8ZP_SCRATCH_REG
rts
.pend
func_log2_stack .proc
jsr func_log2_fac1
jmp push_fac1
.pend
func_log2_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr LOG
jsr MOVEF
lda #<FL_LOG2
ldy #>FL_LOG2
jsr MOVFM
jsr FDIVT
ldx P8ZP_SCRATCH_REG
rts
.pend
func_sign_f_stack .proc
jsr func_sign_f_into_A
sta P8ESTACK_LO,x
dex
rts
.pend
func_sign_f_into_A .proc
jsr MOVFM
jmp SIGN
.pend
func_sqrt_stack .proc
jsr func_sqrt_fac1
jmp push_fac1
.pend
func_sqrt_fac1 .proc
jsr MOVFM
stx P8ZP_SCRATCH_REG
jsr SQR
ldx P8ZP_SCRATCH_REG
rts
.pend
func_rndf_stack .proc
jsr func_rndf_fac1
jmp push_fac1
.pend
func_rndf_fac1 .proc
stx P8ZP_SCRATCH_REG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx P8ZP_SCRATCH_REG
rts
.pend
func_swap_f .proc
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
ldy #4
- lda (P8ZP_SCRATCH_W1),y
pha
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
pla
sta (P8ZP_SCRATCH_W2),y
dey
bpl -
rts
.pend
func_reverse_f .proc
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
_left_index = P8ZP_SCRATCH_W2
_right_index = P8ZP_SCRATCH_W2+1
_loop_count = P8ZP_SCRATCH_REG
pha
jsr a_times_5
sec
sbc #5
sta _right_index
lda #0
sta _left_index
pla
lsr a
sta _loop_count
_loop ; push the left indexed float on the stack
ldy _left_index
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
; copy right index float to left index float
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
; pop the float off the stack into the right index float
ldy _right_index
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
inc _left_index
lda _right_index
sec
sbc #9
sta _right_index
dec _loop_count
bne _loop
rts
.pend
a_times_5 .proc
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1
rts
.pend
func_any_f_into_A .proc
jsr a_times_5
jmp prog8_lib.func_any_b_into_A
.pend
func_all_f_into_A .proc
jsr a_times_5
jmp prog8_lib.func_all_b_into_A
.pend
func_any_f_stack .proc
jsr a_times_5
jmp prog8_lib.func_any_b_stack
.pend
func_all_f_stack .proc
jsr a_times_5
jmp prog8_lib.func_all_b_stack
.pend
func_max_f_stack .proc
jsr func_max_f_fac1
jmp push_fac1
.pend
func_max_f_fac1 .proc
; -- max(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
_loop_count = P8ZP_SCRATCH_REG
stx floats_store_reg
sta _loop_count
lda #255
sta _minmax_cmp+1 ; modifying
lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
- lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr FCOMP
_minmax_cmp cmp #255 ; modified
bne +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr MOVFM
+ lda P8ZP_SCRATCH_W1
clc
adc #5
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ dec _loop_count
bne -
ldx floats_store_reg
rts
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend
func_min_f_stack .proc
jsr func_min_f_fac1
jmp push_fac1
.pend
func_min_f_fac1 .proc
; -- min(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
sta func_max_f_fac1._loop_count
lda #1
sta func_max_f_fac1._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f_fac1._minmax_entry
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend
func_sum_f_stack .proc
jsr func_sum_f_fac1
jmp push_fac1
.pend
func_sum_f_fac1 .proc
; -- sum(array) -> fac1, array in P8ZP_SCRATCH_W1, num elts in A
_loop_count = P8ZP_SCRATCH_REG
stx floats_store_reg
sta _loop_count
lda #<FL_ZERO_const
ldy #>FL_ZERO_const
jsr MOVFM
- lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr FADD
lda P8ZP_SCRATCH_W1
clc
adc #5
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ dec _loop_count
bne -
ldx floats_store_reg
rts
.pend

View File

@ -0,0 +1,253 @@
%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 = %00111011
c64.SCROLX = %00001000
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
clear_screen(1, 0)
}
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
c64.SCROLY = %00011011
c64.SCROLX = %00001000
c64.VMCSB = (c64.VMCSB & %11110000) | %00000100 ; $1000-$2fff
txt.fill_screen(' ', 1)
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
memset(BITMAP_ADDRESS, 320*200/8, 0)
txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; Bresenham algorithm.
; This code special cases various quadrant loops to allow simple ++ and -- operations.
; TODO rewrite this in optimized assembly
if y1>y2 {
; make sure dy is always positive to avoid 8 instead of just 4 special cases
swap(x1, x2)
swap(y1, y2)
}
word @zp d = 0
ubyte positive_ix = true
word @zp dx = x2-x1 as word
word @zp dy = y2-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

@ -0,0 +1,448 @@
; Prog8 definitions for the Commodore-64
; 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 c64
c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $90 ; kernel status variable for I/O
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
&ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword CINV = $0314 ; IRQ vector
&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 default addresses for the character screen chars and colors
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
; the default locations of the 8 sprite pointers (store address of sprite / 64)
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
&ubyte SPRPTR2 = 2042
&ubyte SPRPTR3 = 2043
&ubyte SPRPTR4 = 2044
&ubyte SPRPTR5 = 2045
&ubyte SPRPTR6 = 2046
&ubyte SPRPTR7 = 2047
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
; ---- VIC-II 6567/6569/856x registers ----
&ubyte SP0X = $d000
&ubyte SP0Y = $d001
&ubyte SP1X = $d002
&ubyte SP1Y = $d003
&ubyte SP2X = $d004
&ubyte SP2Y = $d005
&ubyte SP3X = $d006
&ubyte SP3Y = $d007
&ubyte SP4X = $d008
&ubyte SP4Y = $d009
&ubyte SP5X = $d00a
&ubyte SP5Y = $d00b
&ubyte SP6X = $d00c
&ubyte SP6Y = $d00d
&ubyte SP7X = $d00e
&ubyte SP7Y = $d00f
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
&ubyte MSIGX = $d010
&ubyte SCROLY = $d011
&ubyte RASTER = $d012
&ubyte LPENX = $d013
&ubyte LPENY = $d014
&ubyte SPENA = $d015
&ubyte SCROLX = $d016
&ubyte YXPAND = $d017
&ubyte VMCSB = $d018
&ubyte VICIRQ = $d019
&ubyte IREQMASK = $d01a
&ubyte SPBGPR = $d01b
&ubyte SPMC = $d01c
&ubyte XXPAND = $d01d
&ubyte SPSPCL = $d01e
&ubyte SPBGCL = $d01f
&ubyte EXTCOL = $d020 ; border color
&ubyte BGCOL0 = $d021 ; screen color
&ubyte BGCOL1 = $d022
&ubyte BGCOL2 = $d023
&ubyte BGCOL4 = $d024
&ubyte SPMC0 = $d025
&ubyte SPMC1 = $d026
&ubyte SP0COL = $d027
&ubyte SP1COL = $d028
&ubyte SP2COL = $d029
&ubyte SP3COL = $d02a
&ubyte SP4COL = $d02b
&ubyte SP5COL = $d02c
&ubyte SP6COL = $d02d
&ubyte SP7COL = $d02e
&ubyte[8] SPCOL = $d027
; ---- end of VIC-II registers ----
; ---- CIA 6526 1 & 2 registers ----
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
&ubyte CIA1TODHR = $DC0B ; time of day, hours
&ubyte CIA1SDR = $DC0C ; Serial Data Register
&ubyte CIA1ICR = $DC0D
&ubyte CIA1CRA = $DC0E
&ubyte CIA1CRB = $DC0F
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
&ubyte CIA2TODHR = $DD0B ; time of day, hours
&ubyte CIA2SDR = $DD0C ; Serial Data Register
&ubyte CIA2ICR = $DD0D
&ubyte CIA2CRA = $DD0E
&ubyte CIA2CRB = $DD0F
; ---- end of CIA registers ----
; ---- SID 6581/8580 registers ----
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
&uword FREQ1 = $D400 ; channel 1 freq (word)
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
&uword PW1 = $D402 ; channel 1 pulse width (word)
&ubyte CR1 = $D404 ; channel 1 voice control register
&ubyte AD1 = $D405 ; channel 1 attack & decay
&ubyte SR1 = $D406 ; channel 1 sustain & release
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
&uword FREQ2 = $D407 ; channel 2 freq (word)
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
&uword PW2 = $D409 ; channel 2 pulse width (word)
&ubyte CR2 = $D40B ; channel 2 voice control register
&ubyte AD2 = $D40C ; channel 2 attack & decay
&ubyte SR2 = $D40D ; channel 2 sustain & release
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
&uword FREQ3 = $D40E ; channel 3 freq (word)
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
&uword PW3 = $D410 ; channel 3 pulse width (word)
&ubyte CR3 = $D412 ; channel 3 voice control register
&ubyte AD3 = $D413 ; channel 3 attack & decay
&ubyte SR3 = $D414 ; channel 3 sustain & release
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
&uword FC = $D415 ; filter cutoff (word)
&ubyte RESFILT = $D417 ; filter resonance and routing
&ubyte MVOL = $D418 ; filter mode and main volume control
&ubyte POTX = $D419 ; potentiometer X
&ubyte POTY = $D41A ; potentiometer Y
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
&ubyte ENV3 = $D41C ; channel 3 envelope value read
; ---- end of SID registers ----
; ---- C64 ROM kernal routines ----
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- end of C64 ROM kernal routines ----
; ---- 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
}}
}
}

View File

@ -1,969 +0,0 @@
; Prog8 definitions for floating point handling on the Commodore-64
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
%option enable_floats
c64flt {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
; ---- C64 basic and kernal ROM float constants and 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.
; constants in five-byte "mflpt" format in the BASIC ROM
&float FL_PIVAL = $aea8 ; 3.1415926...
&float FL_N32768 = $b1a5 ; -32768
&float FL_FONE = $b9bc ; 1
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
&float FL_SQRTWO = $b9db ; SQR(2)
&float FL_NEGHLF = $b9e0 ; -.5
&float FL_LOG2 = $b9e5 ; LOG(2)
&float FL_TENC = $baf9 ; 10
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
&float FL_FHALF = $bf11 ; .5
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
&float FL_PIHALF = $e2e0 ; PI / 2
&float FL_TWOPI = $e2e5 ; 2 * PI
&float FL_FR4 = $e2ea ; .25
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
; checked functions below:
asmsub MOVFM (uword mflpt @ AY) clobbers(A,Y) = $bba2 ; load mflpt value from memory in A/Y into fac1
asmsub FREADMEM () clobbers(A,Y) = $bba6 ; load mflpt value from memory in $22/$23 into fac1
asmsub CONUPK (uword mflpt @ AY) clobbers(A,Y) = $ba8c ; load mflpt value from memory in A/Y into fac2
asmsub FAREADMEM () clobbers(A,Y) = $ba90 ; load mflpt value from memory in $22/$23 into fac2
asmsub MOVFA () clobbers(A,X) = $bbfc ; copy fac2 to fac1
asmsub MOVAF () clobbers(A,X) = $bc0c ; copy fac1 to fac2 (rounded)
asmsub MOVEF () clobbers(A,X) = $bc0f ; copy fac1 to fac2
asmsub MOVMF (uword mflpt @ XY) clobbers(A,Y) = $bbd4 ; store fac1 to memory X/Y as 5-byte mflpt
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa ; note: calls AYINT.
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
asmsub GETADR () clobbers(X) -> ubyte @ Y, ubyte @ A = $b7f7
asmsub QINT () clobbers(A,X,Y) = $bc9b ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
asmsub AYINT () clobbers(A,X,Y) = $b1bf ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
; there is also c64flt.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
asmsub GIVAYF (ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y) = $b391
asmsub FREADUY (ubyte value @ Y) clobbers(A,X,Y) = $b3a2 ; 8 bit unsigned Y -> float in fac1
asmsub FREADSA (byte value @ A) clobbers(A,X,Y) = $bc3c ; 8 bit signed A -> float in fac1
asmsub FREADSTR (ubyte length @ A) clobbers(A,X,Y) = $b7b5 ; str -> fac1, $22/23 must point to string, A=string length
asmsub FPRINTLN () clobbers(A,X,Y) = $aabc ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
asmsub FOUT () clobbers(X) -> uword @ AY = $bddd ; fac1 -> string, address returned in AY ($0100)
asmsub FADDH () clobbers(A,X,Y) = $b849 ; fac1 += 0.5, for rounding- call this before INT
asmsub MUL10 () clobbers(A,X,Y) = $bae2 ; fac1 *= 10
asmsub DIV10 () clobbers(A,X,Y) = $bafe ; fac1 /= 10 , CAUTION: result is always positive!
asmsub FCOMP (uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A = $bc5b ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
asmsub FADDT () clobbers(A,X,Y) = $b86a ; fac1 += fac2
asmsub FADD (uword mflpt @ AY) clobbers(A,X,Y) = $b867 ; fac1 += mflpt value from A/Y
asmsub FSUBT () clobbers(A,X,Y) = $b853 ; fac1 = fac2-fac1 mind the order of the operands
asmsub FSUB (uword mflpt @ AY) clobbers(A,X,Y) = $b850 ; fac1 = mflpt from A/Y - fac1
asmsub FMULTT () clobbers(A,X,Y) = $ba2b ; fac1 *= fac2
asmsub FMULT (uword mflpt @ AY) clobbers(A,X,Y) = $ba28 ; fac1 *= mflpt value from A/Y
asmsub FDIVT () clobbers(A,X,Y) = $bb12 ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
asmsub FDIV (uword mflpt @ AY) clobbers(A,X,Y) = $bb0f ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
asmsub FPWRT () clobbers(A,X,Y) = $bf7b ; fac1 = fac2 ** fac1
asmsub FPWR (uword mflpt @ AY) clobbers(A,X,Y) = $bf78 ; fac1 = fac2 ** mflpt from A/Y
asmsub NOTOP () clobbers(A,X,Y) = $aed4 ; fac1 = NOT(fac1)
asmsub INT () clobbers(A,X,Y) = $bccc ; INT() truncates, use FADDH first to round instead of trunc
asmsub LOG () clobbers(A,X,Y) = $b9ea ; fac1 = LN(fac1) (natural log)
asmsub SGN () clobbers(A,X,Y) = $bc39 ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
asmsub SIGN () -> ubyte @ A = $bc2b ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
asmsub ABS () = $bc58 ; fac1 = ABS(fac1)
asmsub SQR () clobbers(A,X,Y) = $bf71 ; fac1 = SQRT(fac1)
asmsub SQRA () clobbers(A,X,Y) = $bf74 ; fac1 = SQRT(fac2)
asmsub EXP () clobbers(A,X,Y) = $bfed ; fac1 = EXP(fac1) (e ** fac1)
asmsub NEGOP () clobbers(A) = $bfb4 ; switch the sign of fac1
asmsub RND () clobbers(A,X,Y) = $e097 ; fac1 = RND(fac1) float random number generator
asmsub COS () clobbers(A,X,Y) = $e264 ; fac1 = COS(fac1)
asmsub SIN () clobbers(A,X,Y) = $e26b ; fac1 = SIN(fac1)
asmsub TAN () clobbers(A,X,Y) = $e2b4 ; fac1 = TAN(fac1)
asmsub ATN () clobbers(A,X,Y) = $e30e ; fac1 = ATN(fac1)
asmsub FREADS32 () clobbers(A,X,Y) {
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
%asm {{
lda $62
eor #$ff
asl a
lda #0
ldx #$a0
jmp $bc4f ; internal BASIC routine
}}
}
asmsub FREADUS32 () clobbers(A,X,Y) {
; ---- fac1 = uint32 from $62-$65 big endian (MSB FIRST)
%asm {{
sec
lda #0
ldx #$a0
jmp $bc4f ; internal BASIC routine
}}
}
asmsub FREADS24AXY (ubyte lo @ A, ubyte mid @ X, ubyte hi @ Y) clobbers(A,X,Y) {
; ---- fac1 = signed int24 (A/X/Y contain lo/mid/hi bytes)
; note: there is no FREADU24AXY (unsigned), use FREADUS32 instead.
%asm {{
sty $62
stx $63
sta $64
lda $62
eor #$FF
asl a
lda #0
sta $65
ldx #$98
jmp $bc4f ; internal BASIC routine
}}
}
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
sty $62
sta $63
ldx #$90
sec
jmp $bc49 ; internal BASIC routine
}}
}
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{
sta c64.SCRATCH_ZPREG
tya
ldy c64.SCRATCH_ZPREG
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 c64.SCRATCH_ZPREG
tya
ldy c64.SCRATCH_ZPREG
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 c64.SCRATCH_ZPB1
tya
ldy c64.SCRATCH_ZPB1
rts
}}
}
sub print_f (float value) {
; ---- prints the floating point value (without a newline) using basic rom routines.
%asm {{
stx c64.SCRATCH_ZPREGX
lda #<value
ldy #>value
jsr MOVFM ; load float into fac1
jsr FOUT ; fac1 to string in A/Y
jsr c64.STROUT ; print string in A/Y
ldx c64.SCRATCH_ZPREGX
rts
}}
}
sub print_fln (float value) {
; ---- prints the floating point value (with a newline at the end) using basic rom routines
%asm {{
stx c64.SCRATCH_ZPREGX
lda #<print_fln_value
ldy #>print_fln_value
jsr MOVFM ; load float into fac1
jsr FPRINTLN ; print fac1 with newline
ldx c64.SCRATCH_ZPREGX
rts
}}
}
; --- low level floating point assembly routines
%asm {{
ub2float .proc
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPB1
jsr FREADUY
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
b2float .proc
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPB1
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
uw2float .proc
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
w2float .proc
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
jsr GIVAYF
jmp ub2float._fac_to_mem
.pend
stack_b2float .proc
; -- b2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
jsr FREADSA
jmp push_fac1_as_result
.pend
stack_w2float .proc
; -- w2float operating on the stack
inx
ldy c64.ESTACK_LO,x
lda c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr GIVAYF
jmp push_fac1_as_result
.pend
stack_ub2float .proc
; -- ub2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
tay
jsr FREADUY
jmp push_fac1_as_result
.pend
stack_uw2float .proc
; -- uw2float operating on the stack
inx
lda c64.ESTACK_LO,x
ldy c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr GIVUAYFAY
jmp push_fac1_as_result
.pend
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr AYINT
ldx c64.SCRATCH_ZPREGX
lda $64
sta c64.ESTACK_HI,x
lda $65
sta c64.ESTACK_LO,x
dex
rts
.pend
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr GETADR
ldx c64.SCRATCH_ZPREGX
sta c64.ESTACK_HI,x
tya
sta c64.ESTACK_LO,x
dex
rts
.pend
push_float .proc
; ---- push mflpt5 in A/Y onto stack
; (taking 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_HI,x
dex
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_HI,x
dex
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
dex
rts
.pend
func_rndf .proc
; -- put a random floating point value on the stack
stx c64.SCRATCH_ZPREG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx #<_rndf_rnum5
ldy #>_rndf_rnum5
jsr MOVMF ; fac1 to mem X/Y
ldx c64.SCRATCH_ZPREG
lda #<_rndf_rnum5
ldy #>_rndf_rnum5
jmp push_float
_rndf_rnum5 .byte 0,0,0,0,0
.pend
push_float_from_indexed_var .proc
; -- push the float from the array at A/Y with index on stack, onto the stack.
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp push_float
.pend
pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #4
inx
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
dey
inx
lda c64.ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y
dey
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
dey
inx
lda c64.ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y
dey
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
pop_float_fac1 .proc
; -- pops float from stack into FAC1
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp MOVFM
.pend
pop_float_to_indexed_var .proc
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp pop_float
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
rts
.pend
inc_var_f .proc
; -- add 1 to float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
jsr MOVFM
lda #<FL_FONE
ldy #>FL_FONE
jsr FADD
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
dec_var_f .proc
; -- subtract 1 from float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
lda #<FL_FONE
ldy #>FL_FONE
jsr MOVFM
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FSUB
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
inc_indexed_var_f .proc
; -- add 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp inc_var_f
.pend
dec_indexed_var_f .proc
; -- subtract 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp dec_var_f
.pend
pop_2_floats_f2_in_fac1 .proc
; -- pop 2 floats from stack, load the second one in FAC1 as well
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float2
ldy #>fmath_float2
jmp MOVFM
.pend
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
push_fac1_as_result .proc
; -- push the float in FAC1 onto the stack, and return from calculation
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
lda #<fmath_float1
ldy #>fmath_float1
ldx c64.SCRATCH_ZPREGX
jmp push_float
.pend
pow_f .proc
; -- push f1 ** f2 on stack
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr CONUPK ; fac2 = float1
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
ldx c64.SCRATCH_ZPREGX
jmp push_fac1_as_result
.pend
div_f .proc
; -- push f1/f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FDIV
jmp push_fac1_as_result
.pend
add_f .proc
; -- push f1+f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FADD
jmp push_fac1_as_result
.pend
sub_f .proc
; -- push f1-f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FSUB
jmp push_fac1_as_result
.pend
mul_f .proc
; -- push f1*f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FMULT
jmp push_fac1_as_result
.pend
neg_f .proc
; -- push -flt back on stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr NEGOP
jmp push_fac1_as_result
.pend
abs_f .proc
; -- push abs(float) on stack (as float)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr ABS
jmp push_fac1_as_result
.pend
equal_f .proc
; -- are the two mflpt5 numbers on the stack identical?
inx
inx
inx
inx
lda c64.ESTACK_LO-3,x
cmp c64.ESTACK_LO,x
bne _equals_false
lda c64.ESTACK_LO-2,x
cmp c64.ESTACK_LO+1,x
bne _equals_false
lda c64.ESTACK_LO-1,x
cmp c64.ESTACK_LO+2,x
bne _equals_false
lda c64.ESTACK_HI-2,x
cmp c64.ESTACK_HI+1,x
bne _equals_false
lda c64.ESTACK_HI-1,x
cmp c64.ESTACK_HI+2,x
bne _equals_false
_equals_true lda #1
_equals_store inx
sta c64.ESTACK_LO+1,x
rts
_equals_false lda #0
beq _equals_store
.pend
notequal_f .proc
; -- are the two mflpt5 numbers on the stack different?
jsr equal_f
eor #1 ; invert the result
sta c64.ESTACK_LO+1,x
rts
.pend
less_f .proc
; -- is f1 < f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
bne compare_floats._return_false
.pend
lesseq_f .proc
; -- is f1 <= f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greater_f .proc
; -- is f1 > f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greatereq_f .proc
; -- is f1 >= f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
compare_floats .proc
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr MOVFM ; fac1 = flt1
lda #<fmath_float2
ldy #>fmath_float2
stx c64.SCRATCH_ZPREG
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
ldx c64.SCRATCH_ZPREG
rts
_return_false lda #0
_return_result sta c64.ESTACK_LO,x
dex
rts
_return_true lda #1
bne _return_result
.pend
func_sin .proc
; -- push sin(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr SIN
jmp push_fac1_as_result
.pend
func_cos .proc
; -- push cos(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr COS
jmp push_fac1_as_result
.pend
func_tan .proc
; -- push tan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr TAN
jmp push_fac1_as_result
.pend
func_atan .proc
; -- push atan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr ATN
jmp push_fac1_as_result
.pend
func_ln .proc
; -- push ln(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr LOG
jmp push_fac1_as_result
.pend
func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr LOG
jsr MOVEF
lda #<c64.FL_LOG2
ldy #>c64.FL_LOG2
jsr MOVFM
jsr FDIVT
jmp push_fac1_as_result
.pend
func_sqrt .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr SQR
jmp push_fac1_as_result
.pend
func_rad .proc
; -- convert degrees to radians (d * pi / 180)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_pi_div_180
ldy #>_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend
func_deg .proc
; -- convert radians to degrees (d * (1/ pi * 180))
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend
func_round .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr FADDH
jsr INT
jmp push_fac1_as_result
.pend
func_floor .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr INT
jmp push_fac1_as_result
.pend
func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1
ldy #>fmath_float1
jsr FCOMP
cmp #0
beq +
lda #<FL_FONE
ldy #>FL_FONE
jsr FADD
+ jmp push_fac1_as_result
.pend
func_any_f .proc
inx
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
jmp prog8_lib.func_any_b._entry
.pend
func_all_f .proc
inx
jsr prog8_lib.peek_address
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
tay
dey
- lda (c64.SCRATCH_ZPWORD1),y
clc
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
cmp #0
beq +
cpy #255
bne -
lda #1
sta c64.ESTACK_LO+1,x
rts
+ sta c64.ESTACK_LO+1,x
rts
.pend
func_max_f .proc
lda #255
sta _minmax_cmp+1
lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FCOMP
_minmax_cmp cmp #255 ; modified
bne +
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVFM
+ lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc +
inc c64.SCRATCH_ZPWORD1+1
+ ldy c64.SCRATCH_ZPREG
dey
cpy #255
bne -
jmp push_fac1_as_result
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend
func_min_f .proc
lda #1
sta func_max_f._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f._minmax_entry
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend
func_sum_f .proc
lda #<FL_ZERO
ldy #>FL_ZERO
jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FADD
ldy c64.SCRATCH_ZPREG
dey
cpy #255
beq +
lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc -
inc c64.SCRATCH_ZPWORD1+1
bne -
+ jmp push_fac1_as_result
.pend
sign_f .proc
jsr pop_float_fac1
jsr SIGN
sta c64.ESTACK_LO,x
dex
rts
.pend
}}
} ; ------ end of block c64flt

View File

@ -1,243 +0,0 @@
; Prog8 definitions for the Commodore-64
; Including memory registers, I/O registers, Basic and Kernal subroutines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
c64 {
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
&ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword CINV = $0314 ; IRQ vector
&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 default addresses for the character screen chars and colors
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
; the default locations of the 8 sprite pointers (store address of sprite / 64)
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
&ubyte SPRPTR2 = 2042
&ubyte SPRPTR3 = 2043
&ubyte SPRPTR4 = 2044
&ubyte SPRPTR5 = 2045
&ubyte SPRPTR6 = 2046
&ubyte SPRPTR7 = 2047
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
; ---- VIC-II 6567/6569/856x registers ----
&ubyte SP0X = $d000
&ubyte SP0Y = $d001
&ubyte SP1X = $d002
&ubyte SP1Y = $d003
&ubyte SP2X = $d004
&ubyte SP2Y = $d005
&ubyte SP3X = $d006
&ubyte SP3Y = $d007
&ubyte SP4X = $d008
&ubyte SP4Y = $d009
&ubyte SP5X = $d00a
&ubyte SP5Y = $d00b
&ubyte SP6X = $d00c
&ubyte SP6Y = $d00d
&ubyte SP7X = $d00e
&ubyte SP7Y = $d00f
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
&ubyte MSIGX = $d010
&ubyte SCROLY = $d011
&ubyte RASTER = $d012
&ubyte LPENX = $d013
&ubyte LPENY = $d014
&ubyte SPENA = $d015
&ubyte SCROLX = $d016
&ubyte YXPAND = $d017
&ubyte VMCSB = $d018
&ubyte VICIRQ = $d019
&ubyte IREQMASK = $d01a
&ubyte SPBGPR = $d01b
&ubyte SPMC = $d01c
&ubyte XXPAND = $d01d
&ubyte SPSPCL = $d01e
&ubyte SPBGCL = $d01f
&ubyte EXTCOL = $d020 ; border color
&ubyte BGCOL0 = $d021 ; screen color
&ubyte BGCOL1 = $d022
&ubyte BGCOL2 = $d023
&ubyte BGCOL4 = $d024
&ubyte SPMC0 = $d025
&ubyte SPMC1 = $d026
&ubyte SP0COL = $d027
&ubyte SP1COL = $d028
&ubyte SP2COL = $d029
&ubyte SP3COL = $d02a
&ubyte SP4COL = $d02b
&ubyte SP5COL = $d02c
&ubyte SP6COL = $d02d
&ubyte SP7COL = $d02e
&ubyte[8] SPCOL = $d027
; ---- end of VIC-II registers ----
; ---- CIA 6526 1 & 2 registers ----
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
&ubyte CIA1TODHR = $DC0B ; time of day, hours
&ubyte CIA1SDR = $DC0C ; Serial Data Register
&ubyte CIA1ICR = $DC0D
&ubyte CIA1CRA = $DC0E
&ubyte CIA1CRB = $DC0F
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
&ubyte CIA2TODHR = $DD0B ; time of day, hours
&ubyte CIA2SDR = $DD0C ; Serial Data Register
&ubyte CIA2ICR = $DD0D
&ubyte CIA2CRA = $DD0E
&ubyte CIA2CRB = $DD0F
; ---- end of CIA registers ----
; ---- SID 6581/8580 registers ----
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
&uword FREQ1 = $D400 ; channel 1 freq (word)
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
&uword PW1 = $D402 ; channel 1 pulse width (word)
&ubyte CR1 = $D404 ; channel 1 voice control register
&ubyte AD1 = $D405 ; channel 1 attack & decay
&ubyte SR1 = $D406 ; channel 1 sustain & release
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
&uword FREQ2 = $D407 ; channel 2 freq (word)
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
&uword PW2 = $D409 ; channel 2 pulse width (word)
&ubyte CR2 = $D40B ; channel 2 voice control register
&ubyte AD2 = $D40C ; channel 2 attack & decay
&ubyte SR2 = $D40D ; channel 2 sustain & release
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
&uword FREQ3 = $D40E ; channel 3 freq (word)
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
&uword PW3 = $D410 ; channel 3 pulse width (word)
&ubyte CR3 = $D412 ; channel 3 voice control register
&ubyte AD3 = $D413 ; channel 3 attack & decay
&ubyte SR3 = $D414 ; channel 3 sustain & release
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
&uword FC = $D415 ; filter cutoff (word)
&ubyte RESFILT = $D417 ; filter resonance and routing
&ubyte MVOL = $D418 ; filter mode and main volume control
&ubyte POTX = $D419 ; potentiometer X
&ubyte POTY = $D41A ; potentiometer Y
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
&ubyte ENV3 = $D41C ; channel 3 envelope value read
; ---- end of SID registers ----
; ---- C64 basic routines ----
asmsub CLEARSCR () clobbers(A,X,Y) = $E544 ; clear the screen
asmsub HOMECRSR () clobbers(A,X,Y) = $E566 ; cursor to top left of screen
; ---- end of C64 basic routines ----
; ---- C64 kernal routines ----
asmsub STROUT (uword strptr @ AY) clobbers(A, X, Y) = $AB1E ; print null-terminated string (use c64scr.print instead)
asmsub IRQDFRT () clobbers(A,X,Y) = $EA31 ; default IRQ routine
asmsub IRQDFEND () clobbers(A,X,Y) = $EA81 ; default IRQ end/cleanup
asmsub CINT () clobbers(A,X,Y) = $FF81 ; (alias: SCINIT) initialize screen editor and video chip
asmsub IOINIT () clobbers(A, X) = $FF84 ; initialize I/O devices (CIA, SID, IRQ)
asmsub RAMTAS () clobbers(A,X,Y) = $FF87 ; initialize RAM, tape buffer, screen
asmsub RESTOR () clobbers(A,X,Y) = $FF8A ; restore default I/O vectors
asmsub VECTOR (uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) = $FF8D ; read/set I/O vector table
asmsub SETMSG (ubyte value @ A) = $FF90 ; set Kernal message control flag
asmsub SECOND (ubyte address @ A) clobbers(A) = $FF93 ; (alias: LSTNSA) send secondary address after LISTEN
asmsub TKSA (ubyte address @ A) clobbers(A) = $FF96 ; (alias: TALKSA) send secondary address after TALK
asmsub MEMTOP (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF99 ; read/set top of memory pointer
asmsub MEMBOT (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF9C ; read/set bottom of memory pointer
asmsub SCNKEY () clobbers(A,X,Y) = $FF9F ; scan the keyboard
asmsub SETTMO (ubyte timeout @ A) = $FFA2 ; set time-out flag for IEEE bus
asmsub ACPTR () -> ubyte @ A = $FFA5 ; (alias: IECIN) input byte from serial bus
asmsub CIOUT (ubyte databyte @ A) = $FFA8 ; (alias: IECOUT) output byte to serial bus
asmsub UNTLK () clobbers(A) = $FFAB ; command serial bus device to UNTALK
asmsub UNLSN () clobbers(A) = $FFAE ; command serial bus device to UNLISTEN
asmsub LISTEN (ubyte device @ A) clobbers(A) = $FFB1 ; command serial bus device to LISTEN
asmsub TALK (ubyte device @ A) clobbers(A) = $FFB4 ; command serial bus device to TALK
asmsub READST () -> ubyte @ A = $FFB7 ; read I/O status word
asmsub SETLFS (ubyte logical @ A, ubyte device @ X, ubyte address @ Y) = $FFBA ; set logical file parameters
asmsub SETNAM (ubyte namelen @ A, str filename @ XY) = $FFBD ; set filename parameters
asmsub OPEN () clobbers(A,X,Y) = $FFC0 ; (via 794 ($31A)) open a logical file
asmsub CLOSE (ubyte logical @ A) clobbers(A,X,Y) = $FFC3 ; (via 796 ($31C)) close a logical file
asmsub CHKIN (ubyte logical @ X) clobbers(A,X) = $FFC6 ; (via 798 ($31E)) define an input channel
asmsub CHKOUT (ubyte logical @ X) clobbers(A,X) = $FFC9 ; (via 800 ($320)) define an output channel
asmsub CLRCHN () clobbers(A,X) = $FFCC ; (via 802 ($322)) restore default devices
asmsub CHRIN () clobbers(Y) -> ubyte @ A = $FFCF ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
asmsub CHROUT (ubyte char @ A) = $FFD2 ; (via 806 ($326)) output a character
asmsub LOAD (ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y = $FFD5 ; (via 816 ($330)) load from device
asmsub SAVE (ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A = $FFD8 ; (via 818 ($332)) save to a device
asmsub SETTIM (ubyte low @ A, ubyte middle @ X, ubyte high @ Y) = $FFDB ; set the software clock
asmsub RDTIM () -> ubyte @ A, ubyte @ X, ubyte @ Y = $FFDE ; read the software clock
asmsub STOP () clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc = $FFE1 ; (via 808 ($328)) check the STOP key
asmsub GETIN () clobbers(X,Y) -> ubyte @ A = $FFE4 ; (via 810 ($32A)) get a character
asmsub CLALL () clobbers(A,X) = $FFE7 ; (via 812 ($32C)) close all files
asmsub UDTIM () clobbers(A,X) = $FFEA ; update the software clock
asmsub SCREEN () -> ubyte @ X, ubyte @ Y = $FFED ; read number of screen rows and columns
asmsub PLOT (ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
asmsub IOBASE () -> uword @ XY = $FFF3 ; read base address of I/O devices
; ---- end of C64 kernal routines ----
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,453 @@
; 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 str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the unsigned byte value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
jmp str2uword
}}
}
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the signed byte value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
jmp str2word
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%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
}}
}
asmsub hex2uword(str string @ AY) -> uword @AY {
; -- hexadecimal string with or without '$' to uword.
; string may be in petscii or c64-screencode encoding.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
sty P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'$'
beq _skip
cmp #7
bcc _add_nine
cmp #'9'
beq _calc
bcs _add_nine
_calc asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #$0f
clc
adc P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
_skip inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
_add_nine ldy #9
sty P8ZP_SCRATCH_B1
bne _calc
}}
}
asmsub bin2uword(str string @ AY) -> uword @AY {
; -- binary string with or without '%' to uword.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'%'
beq +
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
}}
}
}

View File

@ -0,0 +1,154 @@
; 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_W2
sty P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_W2
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_W2
tya
ldy P8ZP_SCRATCH_W2
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", ""
%asminclude "library:c64/floats_funcs.asm", ""
}

View File

@ -0,0 +1,164 @@
%target cx16
%import syslib
%import textio
; 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 disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
void cx16.screen_set_mode(2)
txt.fill_screen(' ', 1) ; TODO doesn't seem to fully clear the text screen after returning from gfx mode
}
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 txt.print
; CLEARSCR -> use txt.clear_screen
; HOMECRSR -> use txt.plot
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> ubyte @Pc ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
}
cx16 {
; 65c02 hardware vectors:
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; 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,699 @@
; Prog8 definitions for the Text I/O and Screen routines for the CommanderX16
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
%target cx16
%import syslib
%import conv
txt {
const ubyte DEFAULT_WIDTH = 80
const ubyte DEFAULT_HEIGHT = 60
sub clear_screen() {
clear_screenchars(' ')
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
%asm {{
sty _ly+1
phx
pha
jsr c64.SCREEN ; get dimensions in X/Y
txa
lsr a
lsr a
sta _lx+1
stz cx16.VERA_CTRL
lda #%00010000
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
stz cx16.VERA_ADDR_L ; start at (0,0)
stz cx16.VERA_ADDR_M
pla
_lx ldx #0 ; modified
phy
_ly ldy #1 ; modified
- sta cx16.VERA_DATA0
sty cx16.VERA_DATA0
sta cx16.VERA_DATA0
sty cx16.VERA_DATA0
sta cx16.VERA_DATA0
sty cx16.VERA_DATA0
sta cx16.VERA_DATA0
sty cx16.VERA_DATA0
dex
bne -
ply
dey
beq +
stz cx16.VERA_ADDR_L
inc cx16.VERA_ADDR_M ; next line
bra _lx
+ plx
rts
}}
}
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address)
%asm {{
phx
pha
jsr c64.SCREEN ; get dimensions in X/Y
txa
lsr a
lsr a
sta _lx+1
stz cx16.VERA_CTRL
lda #%00100000
sta cx16.VERA_ADDR_H ; enable auto increment by 2, bank 0.
stz cx16.VERA_ADDR_L ; start at (0,0)
stz cx16.VERA_ADDR_M
pla
_lx ldx #0 ; modified
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
dex
bne -
dey
beq +
stz cx16.VERA_ADDR_L
inc cx16.VERA_ADDR_M ; next line
bra _lx
+ plx
rts
}}
}
asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
; ---- clear the character screen colors with the given color (leaves characters).
; (assumes color matrix is at the default address)
%asm {{
phx
sta _la+1
jsr c64.SCREEN ; get dimensions in X/Y
txa
lsr a
lsr a
sta _lx+1
stz cx16.VERA_CTRL
lda #%00100000
sta cx16.VERA_ADDR_H ; enable auto increment by 2, bank 0.
lda #1
sta cx16.VERA_ADDR_L ; start at (1,0)
stz cx16.VERA_ADDR_M
_lx ldx #0 ; modified
_la lda #0 ; modified
- sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
sta cx16.VERA_DATA0
dex
bne -
dey
beq +
lda #1
sta cx16.VERA_ADDR_L
inc cx16.VERA_ADDR_M ; next line
bra _lx
+ plx
rts
}}
}
ubyte[16] color_to_charcode = [$90,$05,$1c,$9f,$9c,$1e,$1f,$9e,$81,$95,$96,$97,$98,$99,$9a,$9b]
sub color (ubyte txtcol) {
txtcol &= 15
c64.CHROUT(color_to_charcode[txtcol])
}
sub color2 (ubyte txtcol, ubyte bgcol) {
txtcol &= 15
bgcol &= 15
c64.CHROUT(color_to_charcode[bgcol])
c64.CHROUT(1) ; switch fg and bg colors
c64.CHROUT(color_to_charcode[txtcol])
}
sub lowercase() {
cx16.screen_set_charset(3, 0) ; lowercase charset
}
sub uppercase() {
cx16.screen_set_charset(2, 0) ; uppercase charset
}
asmsub scroll_left (ubyte dummy @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
dex
stx _lx+1
dey
sty P8ZP_SCRATCH_B1 ; number of rows to scroll
_nextline
stz cx16.VERA_CTRL ; data port 0: source column
lda #%00010000 ; auto increment 1
sta cx16.VERA_ADDR_H
lda #2
sta cx16.VERA_ADDR_L ; begin in column 1
ldy P8ZP_SCRATCH_B1
sty cx16.VERA_ADDR_M
lda #1
sta cx16.VERA_CTRL ; data port 1: destination column
lda #%00010000 ; auto increment 1
sta cx16.VERA_ADDR_H
stz cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
_lx ldx #0 ; modified
- lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy char
lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy color
dex
bne -
dec P8ZP_SCRATCH_B1
bpl _nextline
lda #0
sta cx16.VERA_CTRL
plx
rts
}}
}
asmsub scroll_right (ubyte dummy @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
dex
stx _lx+1
txa
asl a
dea
sta _rcol+1
ina
ina
sta _rcol2+1
dey
sty P8ZP_SCRATCH_B1 ; number of rows to scroll
_nextline
stz cx16.VERA_CTRL ; data port 0: source column
lda #%00011000 ; auto decrement 1
sta cx16.VERA_ADDR_H
_rcol lda #79*2-1 ; modified
sta cx16.VERA_ADDR_L ; begin in rightmost column minus one
ldy P8ZP_SCRATCH_B1
sty cx16.VERA_ADDR_M
lda #1
sta cx16.VERA_CTRL ; data port 1: destination column
lda #%00011000 ; auto decrement 1
sta cx16.VERA_ADDR_H
_rcol2 lda #79*2+1 ; modified
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
_lx ldx #0 ; modified
- lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy char
lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy color
dex
bne -
dec P8ZP_SCRATCH_B1
bpl _nextline
lda #0
sta cx16.VERA_CTRL
plx
rts
}}
}
asmsub scroll_up (ubyte dummy @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
stx _nextline+1
dey
sty P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL ; data port 0 is source
lda #1
sta cx16.VERA_ADDR_M ; start at second line
stz cx16.VERA_ADDR_L
lda #%00010000
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
lda #1
sta cx16.VERA_CTRL ; data port 1 is destination
stz cx16.VERA_ADDR_M ; start at top line
stz cx16.VERA_ADDR_L
lda #%00010000
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
_nextline
ldx #80 ; modified
- lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy char
lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy color
dex
bne -
dec P8ZP_SCRATCH_B1
beq +
stz cx16.VERA_CTRL ; data port 0
stz cx16.VERA_ADDR_L
inc cx16.VERA_ADDR_M
lda #1
sta cx16.VERA_CTRL ; data port 1
stz cx16.VERA_ADDR_L
inc cx16.VERA_ADDR_M
bra _nextline
+ lda #0
sta cx16.VERA_CTRL
plx
rts
}}
}
asmsub scroll_down (ubyte dummy @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag is a dummy on the cx16
%asm {{
phx
jsr c64.SCREEN
stx _nextline+1
dey
sty P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL ; data port 0 is source
dey
sty cx16.VERA_ADDR_M ; start at line before bottom line
stz cx16.VERA_ADDR_L
lda #%00010000
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
lda #1
sta cx16.VERA_CTRL ; data port 1 is destination
iny
sty cx16.VERA_ADDR_M ; start at bottom line
stz cx16.VERA_ADDR_L
lda #%00010000
sta cx16.VERA_ADDR_H ; enable auto increment by 1, bank 0.
_nextline
ldx #80 ; modified
- lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy char
lda cx16.VERA_DATA0
sta cx16.VERA_DATA1 ; copy color
dex
bne -
dec P8ZP_SCRATCH_B1
beq +
stz cx16.VERA_CTRL ; data port 0
stz cx16.VERA_ADDR_L
dec cx16.VERA_ADDR_M
lda #1
sta cx16.VERA_CTRL ; data port 1
stz cx16.VERA_ADDR_L
dec cx16.VERA_ADDR_M
bra _nextline
+ lda #0
sta cx16.VERA_CTRL
plx
rts
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use c64.CHROUT directly ofcourse.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to c64.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr c64.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
phx
jsr conv.ubyte2decimal
pha
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
txa
jsr c64.CHROUT
plx
rts
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
phx
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr c64.CHROUT
_ones txa
jsr c64.CHROUT
plx
rts
}}
}
asmsub print_b (byte value @ A) clobbers(A,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
phx
pha
cmp #0
bpl +
lda #'-'
jsr c64.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
phx
bcc +
pha
lda #'$'
jsr c64.CHROUT
pla
+ jsr conv.ubyte2hex
jsr c64.CHROUT
tya
jsr c64.CHROUT
plx
rts
}}
}
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
phx
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr c64.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr c64.CHROUT
dey
bne -
plx
rts
}}
}
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
phx
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr c64.CHROUT
iny
bne -
+ plx
rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
phx
jsr conv.uword2decimal
plx
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr c64.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp c64.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr c64.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr c64.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
; ---- sets the character in the screen matrix at the given position
%asm {{
pha
txa
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
}}
}
asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
%asm {{
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
}}
}
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
; ---- set the color in A on the screen matrix at the given position
%asm {{
pha
txa
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
}}
}
asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- get the color in the screen color matrix at the given location
%asm {{
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
%asm {{
phx
lda column
asl a
tax
ldy row
lda charcolor
and #$0f
sta P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
and #$f0
ora P8ZP_SCRATCH_B1
sta cx16.VERA_DATA0
plx
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
%asm {{
phx
tax
clc
jsr c64.PLOT
plx
rts
}}
}
asmsub width() clobbers(X,Y) -> ubyte @A {
; -- returns the text screen width (number of columns)
%asm {{
jsr c64.SCREEN
txa
rts
}}
}
asmsub height() clobbers(X, Y) -> ubyte @A {
; -- returns the text screen height (number of rows)
%asm {{
jsr c64.SCREEN
tya
rts
}}
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
%import textio
test_stack {
asmsub test() {
%asm {{
stx _saveX
lda #13
jsr txt.chrout
lda #'-'
ldy #12
- jsr txt.chrout
dey
bne -
lda #13
jsr txt.chrout
lda #'x'
jsr txt.chrout
lda #'='
jsr txt.chrout
lda _saveX
jsr txt.print_ub
lda #' '
jsr txt.chrout
lda #'s'
jsr txt.chrout
lda #'p'
jsr txt.chrout
lda #'='
jsr txt.chrout
tsx
txa
jsr txt.print_ub
lda #13
jsr txt.chrout
lda #'-'
ldy #12
- jsr txt.chrout
dey
bne -
lda #13
jsr txt.chrout
ldx _saveX
rts
_saveX .byte 0
}}
}
}

View File

@ -1 +1 @@
1.62
5.2

View File

@ -4,8 +4,10 @@ import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.CompilationTarget
import prog8.parser.ParsingFailedError
import prog8.vm.astvm.AstVm
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.StandardWatchEventKinds
@ -31,13 +33,14 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
private fun compileMain(args: Array<String>) {
val cli = CommandLineInterface("prog8compiler")
val startEmulator1 by cli.flagArgument("-emu", "auto-start the 'x64' C-64 emulator after successful compilation")
val startEmulator2 by cli.flagArgument("-emu2", "auto-start the 'x64sc' C-64 emulator after successful compilation")
val startEmulator by cli.flagArgument("-emu", "auto-start emulator after successful compilation")
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
val launchSimulator by cli.flagArgument("-sim", "launch the prog8 virtual machine/simulator after compilation")
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes)")
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
try {
@ -52,32 +55,42 @@ private fun compileMain(args: Array<String>) {
exitProcess(1)
}
if(watchMode && moduleFiles.size<=1) {
if(watchMode) {
val watchservice = FileSystems.getDefault().newWatchService()
while(true) {
val filepath = pathFrom(moduleFiles.single()).normalize()
println("Continuous watch mode active. Main module: $filepath")
println("Continuous watch mode active. Modules: $moduleFiles")
val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
results.add(compilationResult)
}
try {
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) {
print(" ")
println(importedFile)
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
}
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
val allImportedFiles = results.flatMap { it.importedFiles }
println("Imported files (now watching:)")
for (importedFile in allImportedFiles) {
print(" ")
println(importedFile)
val watchDir = importedFile.parent ?: Path.of(".")
watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
}
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
var recompile=false
while(!recompile) {
val event = watchservice.take()
for(changed in event.pollEvents()) {
for (changed in event.pollEvents()) {
val changedPath = changed.context() as Path
println(" change detected: $changedPath")
if(allImportedFiles.any { it.fileName == changedPath.fileName }) {
println(" change detected: $changedPath")
recompile = true
}
}
event.reset()
println("\u001b[H\u001b[2J") // clear the screen
} catch (x: Exception) {
throw x
}
println("\u001b[H\u001b[2J") // clear the screen
}
} else {
@ -85,7 +98,7 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {
@ -94,22 +107,11 @@ private fun compileMain(args: Array<String>) {
exitProcess(1)
}
if (launchSimulator) {
println("\nLaunching AST-based simulator...")
val vm = AstVm(compilationResult.programAst)
vm.run()
}
if (startEmulator1 || startEmulator2) {
if (startEmulator) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else {
val emulator = if(startEmulator1) "x64" else "x64sc"
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 process = ProcessBuilder(cmdline).inheritIO().start()
process.waitFor()
else if(startEmulator) {
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
}
}
}

View File

@ -3,7 +3,6 @@ package prog8.ast
import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.StringDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.processing.IAstVisitor
@ -54,12 +53,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(expr: BinaryExpression) {
output("(")
expr.left.accept(this)
if(expr.operator.any { it.isLetter() })
output(" ${expr.operator} ")
else
output(expr.operator)
expr.right.accept(this)
output(")")
}
override fun visit(directive: Directive) {
@ -79,14 +80,14 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun datatypeString(dt: DataType): String {
return when(dt) {
in NumericDatatypes -> dt.toString().toLowerCase()
in StringDatatypes -> dt.toString().toLowerCase()
DataType.STR -> dt.toString().toLowerCase()
DataType.ARRAY_UB -> "ubyte["
DataType.ARRAY_B -> "byte["
DataType.ARRAY_UW -> "uword["
DataType.ARRAY_W -> "word["
DataType.ARRAY_F -> "float["
DataType.STRUCT -> "" // the name of the struct is enough
else -> "?????2"
else -> "?????"
}
}
@ -103,15 +104,25 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(decl: VarDecl) {
// if the vardecl is a parameter of a subroutine, don't output it again
val paramNames = (decl.definingScope() as? Subroutine)?.parameters?.map { it.name }
if(paramNames!=null && decl.name in paramNames)
return
when(decl.type) {
VarDeclType.VAR -> {}
VarDeclType.CONST -> output("const ")
VarDeclType.MEMORY -> output("&")
}
output(decl.struct?.name ?: "")
if(decl.datatype==DataType.STRUCT && decl.struct!=null)
output(decl.struct!!.name)
output(datatypeString(decl.datatype))
if(decl.arraysize!=null) {
decl.arraysize!!.index.accept(this)
decl.arraysize!!.indexNum?.accept(this)
decl.arraysize!!.indexVar?.accept(this)
}
if(decl.isArray)
output("]")
@ -132,10 +143,9 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
val reg =
when {
param.second.stack -> "stack"
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
param.second.statusflag!=null -> param.second.statusflag.toString()
else -> "?????1"
else -> "?????"
}
output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
if(param.first!==subroutine.parameters.last())
@ -178,8 +188,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun outputStatements(statements: List<Statement>) {
for(stmt in statements) {
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
continue // skip autogenerated decls (to avoid generating a newline)
outputi("")
stmt.accept(this)
output("\n")
@ -197,9 +205,9 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun printout(call: IFunctionCall) {
call.target.accept(this)
output("(")
for(arg in call.arglist) {
for(arg in call.args) {
arg.accept(this)
if(arg!==call.arglist.last())
if(arg!==call.args.last())
output(", ")
}
output(")")
@ -284,20 +292,19 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(assignment: Assignment) {
if(assignment is VariableInitializationAssignment) {
val targetVar = assignment.target.identifier?.targetVarDecl(program.namespace)
if(targetVar?.struct != null) {
// skip STRUCT init assignments
return
}
}
assignment.target.accept(this)
if (assignment.aug_op != null)
output(" ${assignment.aug_op} ")
else
val binExpr = assignment.value as? BinaryExpression
if(binExpr!=null && binExpr.left isSameAs assignment.target
&& binExpr.operator !in setOf("and", "or", "xor")
&& binExpr.operator !in comparisonOperators) {
// we only support the inplace assignments of the form A = A <operator> <value>
assignment.target.accept(this)
output(" ${binExpr.operator}= ")
binExpr.right.accept(this)
} else {
assignment.target.accept(this)
output(" = ")
assignment.value.accept(this)
assignment.value.accept(this)
}
}
override fun visit(postIncrDecr: PostIncrDecr) {
@ -305,20 +312,13 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(postIncrDecr.operator)
}
override fun visit(contStmt: Continue) {
output("continue")
}
override fun visit(breakStmt: Break) {
output("break")
}
override fun visit(forLoop: ForLoop) {
output("for ")
if(forLoop.loopRegister!=null)
output(forLoop.loopRegister.toString())
else
forLoop.loopVar!!.accept(this)
forLoop.loopVar.accept(this)
output(" in ")
forLoop.iterable.accept(this)
output(" ")
@ -334,9 +334,16 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(repeatLoop: RepeatLoop) {
output("repeat ")
repeatLoop.iterations?.accept(this)
output(" ")
repeatLoop.body.accept(this)
}
override fun visit(untilLoop: UntilLoop) {
output("do ")
untilLoop.body.accept(this)
output(" until ")
repeatLoop.untilCondition.accept(this)
untilLoop.condition.accept(this)
}
override fun visit(returnStmt: Return) {
@ -345,19 +352,16 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
arrayIndexedExpression.identifier.accept(this)
arrayIndexedExpression.arrayvar.accept(this)
output("[")
arrayIndexedExpression.arrayspec.index.accept(this)
arrayIndexedExpression.indexer.indexNum?.accept(this)
arrayIndexedExpression.indexer.indexVar?.accept(this)
output("]")
}
override fun visit(assignTarget: AssignTarget) {
if(assignTarget.register!=null)
output(assignTarget.register.toString())
else {
assignTarget.memoryAddress?.accept(this)
assignTarget.identifier?.accept(this)
}
assignTarget.memoryAddress?.accept(this)
assignTarget.identifier?.accept(this)
assignTarget.arrayindexed?.accept(this)
}
@ -398,10 +402,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
outputlni("}}")
}
override fun visit(registerExpr: RegisterExpr) {
output(registerExpr.register.toString())
}
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
output(builtinFunctionStatementPlaceholder.name)
}
@ -435,12 +435,4 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
whenChoice.statements.accept(this)
outputln("")
}
override fun visit(structLv: StructLiteralValue) {
outputListMembers(structLv.values.asSequence(), '{', '}')
}
override fun visit(nopStatement: NopStatement) {
output("; NOP @ ${nopStatement.position} $nopStatement")
}
}

View File

@ -3,6 +3,8 @@ package prog8.ast
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
import java.nio.file.Path
@ -33,13 +35,28 @@ interface Node {
return this
throw FatalAstException("scope missing from $this")
}
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
fun containingStatement(): Statement {
if(this is Statement)
return this
return findParentNode<Statement>(this)!!
}
fun replaceChildNode(node: Node, replacement: Node)
}
interface IFunctionCall {
var target: IdentifierReference
var arglist: MutableList<Expression>
var args: MutableList<Expression>
}
interface INameScope {
val name: String
val position: Position
@ -48,32 +65,31 @@ interface INameScope {
fun linkParents(parent: Node)
fun subScopes(): Map<String, INameScope> {
val subscopes = mutableMapOf<String, INameScope>()
fun subScope(name: String): INameScope? {
for(stmt in statements) {
when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> subscopes[stmt.body.name] = stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
is ForLoop -> if(stmt.body.name==name) return stmt.body
is UntilLoop -> if(stmt.body.name==name) return stmt.body
is WhileLoop -> if(stmt.body.name==name) return stmt.body
is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
}
is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
}
is WhenStatement -> {
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
val scope = stmt.choices.firstOrNull { it.statements.name==name }
if(scope!=null)
return scope.statements
}
is INameScope -> subscopes[stmt.name] = stmt
is INameScope -> if(stmt.name==name) return stmt
else -> {}
}
}
return subscopes
return null
}
fun getLabelOrVariable(name: String): Statement? {
@ -121,7 +137,7 @@ interface INameScope {
for(module in localContext.definingModule().program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScopes()?.get(name)
scope = scope?.subScope(name)
if(scope==null)
break
}
@ -129,19 +145,27 @@ interface INameScope {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScopes()[scopedName.last()] as Statement?
return scope.subScope(scopedName.last()) as Statement?
}
}
return null
} else {
// unqualified name, find the scope the localContext is in, look in that first
// unqualified name
// special case: the do....until statement can also look INSIDE the anonymous scope
if(localContext.parent.parent is UntilLoop) {
val symbolFromInnerScope = (localContext.parent.parent as UntilLoop).body.getLabelOrVariable(scopedName[0])
if(symbolFromInnerScope!=null)
return symbolFromInnerScope
}
// find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope()
val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
@ -158,6 +182,55 @@ interface INameScope {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
}
fun getAllLabels(label: String): List<Label> {
val result = mutableListOf<Label>()
fun find(scope: INameScope) {
scope.statements.forEach {
when(it) {
is Label -> result.add(it)
is INameScope -> find(it)
is IfStatement -> {
find(it.truepart)
find(it.elsepart)
}
is UntilLoop -> find(it.body)
is RepeatLoop -> find(it.body)
is WhileLoop -> find(it.body)
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
else -> { /* do nothing */ }
}
}
}
find(this)
return result
}
fun nextSibling(stmt: Statement): Statement? {
val nextIdx = statements.indexOfFirst { it===stmt } + 1
return if(nextIdx < statements.size)
statements[nextIdx]
else
null
}
fun previousSibling(stmt: Statement): Statement? {
val previousIdx = statements.indexOfFirst { it===stmt } - 1
return if(previousIdx>=0)
statements[previousIdx]
else
null
}
fun indexOfChild(stmt: Statement): Int {
val idx = statements.indexOfFirst { it===stmt }
if(idx>=0)
return idx
else
throw FatalAstException("attempt to find a non-child")
}
}
interface IAssignable {
@ -167,7 +240,7 @@ interface IAssignable {
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String, val modules: MutableList<Module>) {
class Program(val name: String, val modules: MutableList<Module>): Node {
val namespace = GlobalNamespace(modules)
val definedLoadAddress: Int
@ -182,11 +255,29 @@ class Program(val name: String, val modules: MutableList<Module>) {
return if(mainBlocks.isEmpty()) {
null
} else {
mainBlocks[0].subScopes()["start"] as Subroutine?
mainBlocks[0].subScope("start") as Subroutine?
}
}
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
override val position: Position = Position.DUMMY
override var parent: Node
get() = throw FatalAstException("program has no parent")
set(_) = throw FatalAstException("can't set parent of program")
override fun linkParents(parent: Node) {
modules.forEach {
it.linkParents(namespace)
}
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Module && replacement is Module)
val idx = modules.indexOfFirst { it===node }
modules[idx] = replacement
replacement.parent = this
}
}
class Module(override val name: String,
@ -194,12 +285,16 @@ class Module(override val name: String,
override val position: Position,
val isLibraryModule: Boolean,
val source: Path) : Node, INameScope {
override lateinit var parent: Node
lateinit var program: Program
val importedBy = mutableListOf<Module>()
val imports = mutableSetOf<Module>()
var loadAddress: Int = 0 // can be set with the %address directive
val loadAddress: Int by lazy {
val address = (statements.singleOrNull { it is Directive && it.directive == "%address" } as? Directive)?.args?.single()?.int ?: 0
address
}
override fun linkParents(parent: Node) {
this.parent = parent
@ -207,20 +302,34 @@ class Module(override val name: String,
}
override fun definingScope(): INameScope = program.namespace
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Statement && replacement is Statement)
val idx = statements.indexOfFirst { it===node }
statements[idx] = replacement
replacement.parent = this
}
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>()
override val statements = mutableListOf<Statement>() // not used
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {
modules.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("cannot replace anything in the namespace")
}
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
// builtin functions always exist, return a dummy localContext for them
@ -242,12 +351,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.
val stmt = localContext.definingModule().lookup(scopedName, localContext)
return when (stmt) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine, is StructDecl -> stmt
null -> null
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
else -> throw SyntaxError("invalid identifier target type", stmt.position)
}
}
}

View File

@ -7,7 +7,7 @@ import prog8.ast.Module
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.CompilationTarget
import prog8.parser.CustomLexer
import prog8.parser.prog8Parser
import java.io.CharConversionException
@ -19,18 +19,18 @@ import java.nio.file.Path
private data class NumericLiteral(val number: Number, val datatype: DataType)
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source)
val directives = this.directive().map { it.toAst() }
val blocks = this.block().map { it.toAst(isLibrary) }
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), isLibrary, source)
}
private fun ParserRuleContext.toPosition() : Position {
val customTokensource = this.start.tokenSource as? CustomLexer
val filename =
when {
customTokensource!=null -> customTokensource.modulePath.fileName.toString()
customTokensource!=null -> customTokensource.modulePath.toString()
start.tokenSource.sourceName == IntStream.UNKNOWN_SOURCE_NAME -> "@internal@"
else -> File(start.inputStream.sourceName).name
}
@ -38,27 +38,23 @@ private fun ParserRuleContext.toPosition() : Position {
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
}
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : Statement {
val directive = directive()?.toAst()
if(directive!=null) return directive
val block = block()?.toAst(isInLibrary)
if(block!=null) return block
throw FatalAstException(text)
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement {
val blockstatements = block_statement().map {
when {
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
it.directive()!=null -> it.directive().toAst()
it.inlineasm()!=null -> it.inlineasm().toAst()
else -> throw FatalAstException("weird block statement $it")
}
}
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
}
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement =
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList()
private fun prog8Parser.StatementContext.toAst() : Statement {
private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
vardecl()?.let { return it.toAst() }
varinitializer()?.let {
@ -66,7 +62,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.VAR,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@ -114,7 +110,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.CONST,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@ -131,7 +127,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.MEMORY,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@ -142,15 +138,38 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
)
}
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
toPosition())
}
throw FatalAstException("weird variable decl $this")
}
private fun prog8Parser.SubroutinedeclarationContext.toAst() : Subroutine {
return when {
subroutine()!=null -> subroutine().toAst()
asmsubroutine()!=null -> asmsubroutine().toAst()
romsubroutine()!=null -> romsubroutine().toAst()
else -> throw FatalAstException("weird subroutine decl $this")
}
}
private fun prog8Parser.StatementContext.toAst() : Statement {
val vardecl = variabledeclaration()?.toAst()
if(vardecl!=null) return vardecl
assignment()?.let {
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition())
}
augassignment()?.let {
return Assignment(it.assign_target().toAst(),
it.operator.text,
it.expression().toAst(),
it.toPosition())
// replace A += X with A = A + X
val target = it.assign_target().toAst()
val oper = it.operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
}
postincrdecr()?.let {
@ -175,8 +194,8 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val returnstmt = returnstmt()?.toAst()
if(returnstmt!=null) return returnstmt
val sub = subroutine()?.toAst()
if(sub!=null) return sub
val subroutine = subroutinedeclaration()?.toAst()
if(subroutine!=null) return subroutine
val asm = inlineasm()?.toAst()
if(asm!=null) return asm
@ -187,91 +206,115 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val forloop = forloop()?.toAst()
if(forloop!=null) return forloop
val repeatloop = repeatloop()?.toAst()
if(repeatloop!=null) return repeatloop
val untilloop = untilloop()?.toAst()
if(untilloop!=null) return untilloop
val whileloop = whileloop()?.toAst()
if(whileloop!=null) return whileloop
val repeatloop = repeatloop()?.toAst()
if(repeatloop!=null) return repeatloop
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
val asmsubstmt = asmsubroutine()?.toAst()
if(asmsubstmt!=null) return asmsubstmt
val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
toPosition())
}
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, null, true, statements, toPosition())
}
private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val address = integerliteral().toAst().number.toInt()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, mutableListOf(), toPosition())
}
private class AsmsubDecl(val name: String,
val parameters: List<SubroutineParameter>,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<CpuRegister>)
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
val name = identifier().text
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
val params = asmsub_params()?.toAst() ?: emptyList()
val returns = asmsub_returns()?.toAst() ?: emptyList()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
val normalReturnvalues = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(name, normalParameters, normalReturnvalues,
paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition())
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
val normalReturntypes = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag) }
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
}
private class AsmSubroutineParameter(name: String,
type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
val stack: Boolean,
position: Position) : SubroutineParameter(name, type, position)
private class AsmSubroutineReturn(val type: DataType,
val registerOrPair: RegisterOrPair?,
val statusflag: Statusflag?,
val stack: Boolean,
val position: Position)
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
= this.register().asSequence().map { it.toAst() }.toSet()
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
= asmsub_return().map {
val register = it.identifier()?.toAst()
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (val name = register.nameInSource.single()) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
else -> throw FatalAstException("invalid register or status flag in $it")
}
}
AsmSubroutineReturn(
it.datatype().toAst(),
registerorpair,
statusregister,
toPosition())
}
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map {
val vardecl = it.vardecl()
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
AsmSubroutineParameter(vardecl.varname.text, datatype,
it.registerorpair()?.toAst(),
it.statusregister()?.toAst(),
!it.stack?.text.isNullOrEmpty(), toPosition())
val register = it.identifier()?.toAst()
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (val name = register.nameInSource.single()) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
else -> throw FatalAstException("invalid register or status flag '$name'")
}
}
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
}
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
val void = this.VOID() != null
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), toPosition())
FunctionCallStatement(location, mutableListOf(), void, toPosition())
else
FunctionCallStatement(location, expression_list().toAst().toMutableList(), toPosition())
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
}
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
@ -280,11 +323,9 @@ private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun prog8Parser.InlineasmContext.toAst() =
InlineAssembly(INLINEASMBLOCK().text, toPosition())
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
return Return(expression()?.toAst(), toPosition())
}
@ -295,20 +336,15 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
return Jump(address, identifier, null, toPosition())
}
private fun prog8Parser.LabeldefContext.toAst(): Statement =
Label(children[0].text, toPosition())
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
// non-asm subroutine
val returntypes = sub_return_part()?.toAst() ?: emptyList()
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
sub_return_part()?.toAst() ?: emptyList(),
emptyList(),
emptyList(),
emptySet(),
null,
false,
returntypes,
statement_block()?.toAst() ?: mutableListOf(),
toPosition())
}
@ -318,47 +354,45 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
return returns.datatype().map { it.toAst() }
}
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
SubroutineParameter(it.varname.text, datatype, it.toPosition())
}
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
val register = register()?.toAst()
val identifier = scoped_identifier()
return when {
register!=null -> AssignTarget(register, null, null, null, toPosition())
identifier!=null -> AssignTarget(null, identifier.toAst(), null, null, toPosition())
arrayindexed()!=null -> AssignTarget(null, null, arrayindexed().toAst(), null, toPosition())
directmemory()!=null -> AssignTarget(null, null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
else -> AssignTarget(null, scoped_identifier()?.toAst(), null, null, toPosition())
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
}
}
private fun prog8Parser.RegisterContext.toAst() = Register.valueOf(text.toUpperCase())
private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
val names = this.identifier().map { it.toAst().nameInSource.single() }
return names.map { CpuRegister.valueOf(it) }.toSet()
}
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
private fun prog8Parser.DirectiveContext.toAst() : Directive =
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
val str = stringliteral()
if(str?.ALT_STRING_ENCODING() != null)
throw AstException("${toPosition()} can't use alternate string encodings for directive arguments")
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg =
DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
}
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
fun makeLiteral(text: String, radix: Int): NumericLiteral {
val integer: Int
var datatype = DataType.UBYTE
when (radix) {
@ -396,19 +430,18 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
}
else -> throw FatalAstException("invalid radix")
}
return NumericLiteral(integer, if (forceWord) DataType.UWORD else datatype)
return NumericLiteral(integer, datatype)
}
val terminal: TerminalNode = children[0] as TerminalNode
val integerPart = this.intpart.text
return when (terminal.symbol.type) {
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10, wordsuffix()!=null)
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16, wordsuffix()!=null)
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2, wordsuffix()!=null)
prog8Parser.DEC_INTEGER -> makeLiteral(integerPart, 10)
prog8Parser.HEX_INTEGER -> makeLiteral(integerPart.substring(1), 16)
prog8Parser.BIN_INTEGER -> makeLiteral(integerPart.substring(1), 2)
else -> throw FatalAstException(terminal.text)
}
}
private fun prog8Parser.ExpressionContext.toAst() : Expression {
val litval = literalvalue()
@ -429,10 +462,12 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
else -> throw FatalAstException("invalid datatype for numeric literal")
}
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
litval.stringliteral()!=null -> StringLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), litval.toPosition())
litval.stringliteral()!=null -> litval.stringliteral().toAst()
litval.charliteral()!=null -> {
try {
NumericLiteralValue(DataType.UBYTE, Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], litval.toPosition())
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
} catch (ce: CharConversionException) {
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
}
@ -441,20 +476,13 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
val array = litval.arrayliteral().toAst()
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFold takes care of that and converts the type if needed.
ArrayLiteralValue(DataType.ARRAY_UB, array, position = litval.toPosition())
}
litval.structliteral()!=null -> {
val values = litval.structliteral().expression().map { it.toAst() }
StructLiteralValue(values, litval.toPosition())
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
}
else -> throw FatalAstException("invalid parsed literal")
}
}
}
if(register()!=null)
return RegisterExpr(register().toAst(), register().toPosition())
if(scoped_identifier()!=null)
return scoped_identifier().toAst()
@ -468,7 +496,8 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
if(funcall!=null) return funcall
if (rangefrom!=null && rangeto!=null) {
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, 1, toPosition())
val defaultstep = if(rto.text == "to") 1 else -1
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
}
@ -490,6 +519,8 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
throw FatalAstException(text)
}
private fun prog8Parser.StringliteralContext.toAst(): StringLiteralValue =
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
return ArrayIndexedExpression(scoped_identifier().toAst(),
@ -497,28 +528,22 @@ private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
toPosition())
}
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
IdentifierReference(listOf(text), toPosition())
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
IdentifierReference(NAME().map { it.text }, toPosition())
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
"true" -> true
"false" -> false
else -> throw FatalAstException(text)
}
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
expression().map { it.toAst() }.toTypedArray()
@ -536,7 +561,6 @@ private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
}
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@ -549,25 +573,19 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst()
val loopvar = identifier()?.toAst()
val loopvar = identifier().toAst()
val iterable = expression()!!.toAst()
val scope =
if(statement()!=null)
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
else
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
return ForLoop(loopregister, loopvar, iterable, scope, toPosition())
return ForLoop(loopvar, iterable, scope, toPosition())
}
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
val condition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@ -576,13 +594,20 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
return WhileLoop(condition, scope, toPosition())
}
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
val iterations = expression()?.toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return RepeatLoop(iterations, scope, toPosition())
}
private fun prog8Parser.UntilloopContext.toAst(): UntilLoop {
val untilCondition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return RepeatLoop(scope, untilCondition, toPosition())
return UntilLoop(scope, untilCondition, toPosition())
}
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
@ -616,7 +641,20 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
)
}
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
internal fun escape(str: String): String {
val es = str.map {
when(it) {
'\t' -> "\\t"
'\n' -> "\\n"
'\r' -> "\\r"
'"' -> "\\\""
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
in '\u0000'..'\u00ff' -> it.toString()
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
}
}
return es.joinToString("")
}
internal fun unescape(str: String, position: Position): String {
val result = mutableListOf<Char>()
@ -630,9 +668,15 @@ internal fun unescape(str: String, position: Position): String {
'n' -> '\n'
'r' -> '\r'
'"' -> '"'
'\'' -> '\''
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
'x' -> {
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
(0x8000 + hex).toChar()
}
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
})
} else {
@ -641,4 +685,3 @@ internal fun unescape(str: String, position: Position): String {
}
return result.joinToString("")
}

View File

@ -1,7 +1,8 @@
package prog8.ast.base
import prog8.ast.Node
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.CompilationTarget
/**************************** AST Data classes ****************************/
@ -12,7 +13,6 @@ enum class DataType {
WORD, // pass by value
FLOAT, // pass by value
STR, // pass by reference
STR_S, // pass by reference
ARRAY_UB, // pass by reference
ARRAY_B, // pass by reference
ARRAY_UW, // pass by reference
@ -21,36 +21,39 @@ enum class DataType {
STRUCT; // pass by reference
/**
* is the type assignable to the given other type?
* is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
*/
infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision?
when(this) {
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
UWORD -> targetType in setOf(UWORD, FLOAT)
WORD -> targetType in setOf(WORD, FLOAT)
FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType==STR_S
STR_S -> targetType == STR || targetType==STR_S
in ArrayDatatypes -> targetType == this
STR -> targetType == STR || targetType == UWORD
in ArrayDatatypes -> targetType == this || targetType == UWORD
else -> false
}
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
infix fun isNotAssignableTo(targetType: DataType) = !this.isAssignableTo(targetType)
infix fun isNotAssignableTo(targetTypes: Set<DataType>) = !this.isAssignableTo(targetTypes)
infix fun largerThan(other: DataType) =
when(this) {
in ByteDatatypes -> false
in WordDatatypes -> other in ByteDatatypes
when {
this == other -> false
this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> false
else -> true
}
infix fun equalsSize(other: DataType) =
when(this) {
in ByteDatatypes -> other in ByteDatatypes
in WordDatatypes -> other in WordDatatypes
when {
this == other -> true
this in ByteDatatypes -> other in ByteDatatypes
this in WordDatatypes -> other in WordDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> true
else -> false
}
@ -58,14 +61,14 @@ enum class DataType {
return when(this) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
FLOAT -> MachineDefinition.Mflpt5.MemorySize
in PassByReferenceDatatypes -> 2
FLOAT -> CompilationTarget.instance.machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> CompilationTarget.instance.machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
enum class Register {
enum class CpuRegister {
A,
X,
Y
@ -77,14 +80,25 @@ enum class RegisterOrPair {
Y,
AX,
AY,
XY
XY,
FAC1,
FAC2;
companion object {
val names by lazy { values().map { it.toString()} }
}
} // only used in parameter and return value specs in asm subroutines
enum class Statusflag {
Pc,
Pz,
Pv,
Pn
Pn;
companion object {
val names by lazy { values().map { it.toString()} }
}
}
enum class BranchCondition {
@ -112,10 +126,9 @@ val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
val IterableDatatypes = setOf(
DataType.STR, DataType.STR_S,
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
@ -123,12 +136,18 @@ val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
val ArrayElementTypes = mapOf(
DataType.STR to DataType.UBYTE,
DataType.STR_S to DataType.UBYTE,
DataType.ARRAY_B to DataType.BYTE,
DataType.ARRAY_UB to DataType.UBYTE,
DataType.ARRAY_W to DataType.WORD,
DataType.ARRAY_UW to DataType.UWORD,
DataType.ARRAY_F to DataType.FLOAT)
val ElementArrayTypes = mapOf(
DataType.BYTE to DataType.ARRAY_B,
DataType.UBYTE to DataType.ARRAY_UB,
DataType.WORD to DataType.ARRAY_W,
DataType.UWORD to DataType.ARRAY_UW,
DataType.FLOAT to DataType.ARRAY_F
)
// find the parent node of a specific type or interface
// (useful to figure out in what namespace/block something is defined, etc)
@ -146,8 +165,16 @@ object ParentSentinel : Node {
override val position = Position("<<sentinel>>", 0, 0, 0)
override var parent: Node = this
override fun linkParents(parent: Node) {}
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
}
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
fun toClickableStr(): String = "$file:$line:$startCol:"
companion object {
val DUMMY = Position("<dummy>", 0, 0, 0)
}
}

View File

@ -3,35 +3,42 @@ package prog8.ast.base
import prog8.parser.ParsingFailedError
fun printErrors(errors: List<Any>, moduleName: String) {
val reportedMessages = mutableSetOf<String>()
System.err.print("\u001b[91m") // bright red
errors.forEach {
val msg = it.toString()
if(msg !in reportedMessages) {
System.err.println(msg)
reportedMessages.add(msg)
}
class ErrorReporter {
private enum class MessageSeverity {
WARNING,
ERROR
}
System.err.print("\u001b[0m") // reset color
if(reportedMessages.isNotEmpty())
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
}
fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
print("\u001b[93m") // bright yellow
print("$position Warning: $msg")
if(detailInfo==null)
print("\n")
else
println(": $detailInfo\n")
print("\u001b[0m") // normal
}
fun printWarning(msg: String) {
print("\u001b[93m") // bright yellow
print("Warning: $msg")
print("\u001b[0m\n") // normal
private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position)
private val messages = mutableListOf<CompilerMessage>()
private val alreadyReportedMessages = mutableSetOf<String>()
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
fun handle() {
var numErrors = 0
var numWarnings = 0
messages.forEach {
when(it.severity) {
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
}
val msg = "${it.position.toClickableStr()} ${it.severity} ${it.message}".trim()
if(msg !in alreadyReportedMessages) {
System.err.println(msg)
alreadyReportedMessages.add(msg)
when(it.severity) {
MessageSeverity.WARNING -> numWarnings++
MessageSeverity.ERROR -> numErrors++
}
}
System.err.print("\u001b[0m") // reset color
}
messages.clear()
if(numErrors>0)
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
}
fun isEmpty() = messages.isEmpty()
}

View File

@ -2,21 +2,17 @@ package prog8.ast.base
import prog8.ast.expressions.IdentifierReference
class FatalAstException (override var message: String) : Exception(message)
open class FatalAstException (override var message: String) : Exception(message)
open class AstException (override var message: String) : Exception(message)
class SyntaxError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Syntax error: $message"
}
open class NameError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Name error: $message"
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "${position.toClickableStr()} Syntax error: $message"
}
class ExpressionError(message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Error: $message"
override fun toString() = "${position.toClickableStr()} Error: $message"
}
class UndefinedSymbolError(symbol: IdentifierReference)
: NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)

View File

@ -3,68 +3,100 @@ package prog8.ast.base
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.ast.statements.Directive
import prog8.compiler.CompilationOptions
import prog8.compiler.target.c64.codegen.AnonymousScopeVarsCleanup
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
import prog8.compiler.BeforeAsmGenerationAstChanger
// the name of the subroutine that should be called for every block to initialize its variables
internal const val initvarsSubName="prog8_init_vars"
internal fun Program.removeNopsFlattenAnonScopes() {
val flattener = FlattenAnonymousScopesAndRemoveNops()
flattener.visit(this)
}
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(this, compilerOptions)
checker.visit(this)
printErrors(checker.result(), name)
}
internal fun Program.anonscopeVarsCleanup() {
val mover = AnonymousScopeVarsCleanup(this)
mover.visit(this)
printErrors(mover.result(), name)
}
internal fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(this)
initvalueCreator.visit(this)
val checker = StatementReorderer(this)
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
val checker = AstChecker(this, compilerOptions, errors)
checker.visit(this)
}
internal fun Program.addTypecasts() {
val caster = TypecastsAdder(this)
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
val fixer = BeforeAsmGenerationAstChanger(this, errors)
fixer.visit(this)
fixer.applyModifications()
}
internal fun Program.reorderStatements(errors: ErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
reorder.applyModifications()
}
internal fun Program.addTypecasts(errors: ErrorReporter) {
val caster = TypecastsAdder(this, errors)
caster.visit(this)
caster.applyModifications()
}
internal fun Program.verifyFunctionArgTypes() {
val fixer = VerifyFunctionArgTypes(this)
fixer.visit(this)
}
internal fun Module.checkImportedValid() {
val checker = ImportedModuleDirectiveRemover()
checker.visit(this)
printErrors(checker.result(), name)
val imr = ImportedModuleDirectiveRemover()
imr.visit(this, this.parent)
imr.applyModifications()
}
internal fun Program.checkRecursion() {
val checker = AstRecursionChecker(namespace)
checker.visit(this)
printErrors(checker.result(), name)
}
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val checker2 = AstIdentifiersChecker(this, errors)
checker2.visit(this)
internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(this)
checker.visit(this)
if(modules.map {it.name}.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
if(errors.isEmpty()) {
val transforms = AstVariousTransforms(this)
transforms.visit(this)
transforms.applyModifications()
val lit2decl = LiteralsToAutoVars(this)
lit2decl.visit(this)
lit2decl.applyModifications()
}
printErrors(checker.result(), name)
if (modules.map { it.name }.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
}
}
internal fun Program.variousCleanups() {
val process = VariousCleanups()
process.visit(this)
process.applyModifications()
}
internal fun Program.moveMainAndStartToFirst() {
// the module containing the program entrypoint is moved to the first in the sequence.
// the "main" block containing the entrypoint is moved to the top in there,
// and finally the entrypoint subroutine "start" itself is moved to the top in that block.
val directives = modules[0].statements.filterIsInstance<Directive>()
val start = this.entrypoint()
if(start!=null) {
val mod = start.definingModule()
val block = start.definingBlock()
if(!modules.remove(mod))
throw FatalAstException("module wrong")
modules.add(0, mod)
mod.remove(block)
var afterDirective = mod.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
mod.statements.add(block)
else
mod.statements.add(afterDirective, block)
block.remove(start)
afterDirective = block.statements.indexOfFirst { it !is Directive }
if(afterDirective<0)
block.statements.add(start)
else
block.statements.add(afterDirective, start)
// overwrite the directives in the module containing the entrypoint
for(directive in directives) {
modules[0].statements.removeAll { it is Directive && it.directive == directive.directive }
modules[0].statements.add(0, directive)
}
}
}

View File

@ -3,49 +3,65 @@ package prog8.ast.expressions
import prog8.ast.*
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.compiler.target.c64.Petscii
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import prog8.functions.CannotEvaluateException
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import java.util.Objects
import java.util.*
import kotlin.math.abs
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
val comparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val augmentAssignmentOperators = setOf("+", "-", "/", "*", "**", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstModifyingVisitor): Expression
abstract fun accept(visitor: IAstVisitor)
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
abstract fun accept(visitor: AstWalker, parent: Node)
abstract fun referencesIdentifier(vararg scopedName: String): Boolean
abstract fun inferType(program: Program): InferredTypes.InferredType
infix fun isSameAs(assigntarget: AssignTarget) = assigntarget.isSameAs(this)
infix fun isSameAs(other: Expression): Boolean {
if(this===other)
return true
when(this) {
is RegisterExpr ->
return (other is RegisterExpr && other.register==register)
return when(this) {
is IdentifierReference ->
return (other is IdentifierReference && other.nameInSource==nameInSource)
(other is IdentifierReference && other.nameInSource==nameInSource)
is PrefixExpression ->
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
(other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
is BinaryExpression ->
return (other is BinaryExpression && other.operator==operator
(other is BinaryExpression && other.operator==operator
&& other.left isSameAs left
&& other.right isSameAs right)
is ArrayIndexedExpression -> {
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index)
(other is ArrayIndexedExpression && other.arrayvar.nameInSource == arrayvar.nameInSource
&& other.indexer isSameAs indexer)
}
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
}
}
}
@ -59,11 +75,38 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
expression.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node === expression && replacement is Expression)
expression = replacement
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType = expression.inferType(program)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val inferred = expression.inferType(program)
return when(operator) {
"+" -> inferred
"~", "not" -> {
when(inferred.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
in WordDatatypes -> InferredTypes.knownFor(DataType.UWORD)
else -> inferred
}
}
"-" -> {
when(inferred.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> InferredTypes.knownFor(DataType.BYTE)
in WordDatatypes -> InferredTypes.knownFor(DataType.WORD)
else -> inferred
}
}
else -> throw FatalAstException("weird prefix expression operator")
}
}
override fun toString(): String {
return "Prefix($operator $expression)"
@ -79,6 +122,16 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
right.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
when {
node===left -> left = replacement
node===right -> right = replacement
else -> throw FatalAstException("invalid replace, no child $node")
}
replacement.parent = this
}
override fun toString(): String {
return "[$left $operator $right]"
}
@ -86,9 +139,10 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
// binary expression should actually have been optimized away into a single value, before const value was requested...
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = left.referencesIdentifier(*scopedName) || right.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program)
val rightDt = right.inferType(program)
@ -178,26 +232,35 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
}
}
class ArrayIndexedExpression(var identifier: IdentifierReference,
val arrayspec: ArrayIndex,
class ArrayIndexedExpression(var arrayvar: IdentifierReference,
val indexer: ArrayIndex,
override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
identifier.linkParents(this)
arrayspec.linkParents(this)
arrayvar.linkParents(this)
indexer.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===arrayvar -> arrayvar = replacement as IdentifierReference
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = arrayvar.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val target = identifier.targetStatement(program.namespace)
val target = arrayvar.targetStatement(program.namespace)
if (target is VarDecl) {
return when (target.datatype) {
in StringDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(target.datatype))
else -> InferredTypes.unknown()
}
@ -206,7 +269,7 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
}
override fun toString(): String {
return "ArrayIndexed(ident=$identifier, arraysize=$arrayspec; pos=$position)"
return "ArrayIndexed(ident=$arrayvar, arraysize=$indexer; pos=$position)"
}
}
@ -218,15 +281,24 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
expression.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===expression)
expression = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = expression.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? {
val cv = expression.constValue(program) ?: return null
return cv.cast(type)
// val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
// return LiteralValue.fromNumber(value.numericValue(), value.type, position).cast(type)
val cast = cv.cast(type)
return if(cast.isValid)
cast.valueOrZero()
else
null
}
override fun toString(): String {
@ -242,11 +314,17 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
identifier.parent=this
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is IdentifierReference && node===identifier)
identifier = replacement
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifiers(vararg name: String) = false
override fun referencesIdentifier(vararg scopedName: String) = false
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
}
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression(), IAssignable {
@ -257,9 +335,16 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
this.addressExpression.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression)
addressExpression = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = false
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String) = false
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
override fun constValue(program: Program): NumericLiteralValue? = null
@ -302,17 +387,21 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
}
}
val asBooleanValue: Boolean = number!=0
val asBooleanValue: Boolean = number.toDouble() != 0.0
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun referencesIdentifiers(vararg name: String) = false
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun referencesIdentifier(vararg scopedName: String) = false
override fun constValue(program: Program) = this
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
@ -328,138 +417,129 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
fun cast(targettype: DataType): NumericLiteralValue {
class CastValue(val isValid: Boolean, private val value: NumericLiteralValue?) {
fun valueOrZero() = if(isValid) value!! else NumericLiteralValue(DataType.UBYTE, 0, Position.DUMMY)
}
fun cast(targettype: DataType): CastValue {
if(type==targettype)
return this
return CastValue(true, this)
val numval = number.toDouble()
when(type) {
DataType.UBYTE -> {
if(targettype== DataType.BYTE && numval <= 127)
return NumericLiteralValue(targettype, number.toShort(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.WORD || targettype== DataType.UWORD)
return NumericLiteralValue(targettype, number.toInt(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
}
DataType.BYTE -> {
if(targettype== DataType.UBYTE && numval >= 0)
return NumericLiteralValue(targettype, number.toShort(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UWORD && numval >= 0)
return NumericLiteralValue(targettype, number.toInt(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.WORD)
return NumericLiteralValue(targettype, number.toInt(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
}
DataType.UWORD -> {
if(targettype== DataType.BYTE && numval <= 127)
return NumericLiteralValue(targettype, number.toShort(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UBYTE && numval <= 255)
return NumericLiteralValue(targettype, number.toShort(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.WORD && numval <= 32767)
return NumericLiteralValue(targettype, number.toInt(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
}
DataType.WORD -> {
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
return NumericLiteralValue(targettype, number.toShort(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
return NumericLiteralValue(targettype, number.toShort(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if(targettype== DataType.UWORD && numval >=0)
return NumericLiteralValue(targettype, number.toInt(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toDouble(), position))
}
DataType.FLOAT -> {
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
return NumericLiteralValue(targettype, number.toShort(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
return NumericLiteralValue(targettype, number.toShort(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toShort(), position))
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
return NumericLiteralValue(targettype, number.toInt(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
return NumericLiteralValue(targettype, number.toInt(), position)
return CastValue(true, NumericLiteralValue(targettype, number.toInt(), position))
}
else -> {}
}
throw ExpressionError("can't cast $type into $targettype", position)
return CastValue(false, null)
}
}
class StructLiteralValue(var values: List<Expression>,
override val position: Position): Expression() {
override lateinit var parent: Node
private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
override fun linkParents(parent: Node) {
this.parent=parent
values.forEach { it.linkParents(this) }
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun 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(", ")} }"
}
}
class StringLiteralValue(val type: DataType, // only string types
val value: String,
class StringLiteralValue(val value: String,
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
override val position: Position) : Expression() {
override lateinit var parent: Node
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun referencesIdentifiers(vararg name: String) = false
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun referencesIdentifier(vararg scopedName: String) = false
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "'${escape(value)}'"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STR)
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
override fun hashCode(): Int = Objects.hash(value, type)
override fun hashCode(): Int = Objects.hash(value, altEncoding)
override fun equals(other: Any?): Boolean {
if(other==null || other !is StringLiteralValue)
return false
return value==other.value && type==other.type
}
var heapId: Int? = null
private set
fun addToHeap() {
if(heapId==null)
heapId = ++heapIdSequence
return value==other.value && altEncoding == other.altEncoding
}
}
private var heapIdSequence = 0
class ArrayLiteralValue(val type: DataType, // only array types
class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred because not all array literals hava a known type yet
val value: Array<Expression>,
initHeapId: Int? =null,
override val position: Position) : Expression() {
override lateinit var parent: Node
var heapId = initHeapId
private set
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) {
this.parent = parent
value.forEach {it.linkParents(this)}
}
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
val idx = value.indexOfFirst { it===node }
value[idx] = replacement
replacement.parent = this
}
override fun referencesIdentifier(vararg scopedName: String) = value.any { it.referencesIdentifier(*scopedName) }
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "$value"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) type else guessDatatype(program)
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
override fun hashCode(): Int = Objects.hash(value, type)
override fun equals(other: Any?): Boolean {
@ -468,35 +548,65 @@ class ArrayLiteralValue(val type: DataType, // only array types
return type==other.type && value.contentEquals(other.value)
}
fun guessDatatype(program: Program): InferredTypes.InferredType {
// Educated guess of the desired array literal's datatype.
// If it's inside a for loop, assume the data type of the loop variable is what we want.
val forloop = parent as? ForLoop
if(forloop != null) {
val loopvarDt = forloop.loopVarDt(program)
if(loopvarDt.isKnown) {
return if(loopvarDt.typeOrElse(DataType.STRUCT) !in ElementArrayTypes)
InferredTypes.InferredType.unknown()
else
InferredTypes.InferredType.known(ElementArrayTypes.getValue(loopvarDt.typeOrElse(DataType.STRUCT)))
}
}
// otherwise, select the "biggegst" datatype based on the elements in the array.
val datatypesInArray = value.map { it.inferType(program) }
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when {
DataType.FLOAT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_F)
DataType.STR in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
DataType.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB)
DataType.ARRAY_UW in dts ||
DataType.ARRAY_W in dts ||
DataType.ARRAY_UB in dts ||
DataType.ARRAY_B in dts ||
DataType.ARRAY_F in dts ||
DataType.STRUCT in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
else -> InferredTypes.InferredType.unknown()
}
}
fun cast(targettype: DataType): ArrayLiteralValue? {
if(type==targettype)
if(type.istype(targettype))
return this
if(targettype in ArrayDatatypes) {
val elementType = ArrayElementTypes.getValue(targettype)
val castArray = value.map{
val num = it as? NumericLiteralValue
if(num==null) {
// an array of UWORDs could possibly also contain AddressOfs
// an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted
if (elementType != DataType.UWORD || it !is AddressOf)
throw FatalAstException("weird array element $it")
return null // can't cast a value of the array, abort
it
} else {
try {
num.cast(elementType)
} catch(x: ExpressionError) {
return null
}
val cast = num.cast(elementType)
if(cast.isValid)
cast.valueOrZero()
else
return null // can't cast a value of the array, abort
}
}.toTypedArray()
return ArrayLiteralValue(targettype, castArray, position = position)
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
}
return null // invalid type conversion from $this to $targettype
}
fun addToHeap() {
if(heapId==null)
heapId = ++heapIdSequence
}
}
class RangeExpr(var from: Expression,
@ -512,10 +622,22 @@ class RangeExpr(var from: Expression,
step.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
when {
from===node -> from=replacement
to===node -> to=replacement
step===node -> step=replacement
else -> throw FatalAstException("invalid replacement")
}
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String): Boolean = from.referencesIdentifier(*scopedName) || to.referencesIdentifier(*scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program)
val toDt=to.inferType(program)
@ -524,10 +646,16 @@ class RangeExpr(var from: Expression,
fromDt istype DataType.UBYTE && toDt istype DataType.UBYTE -> InferredTypes.knownFor(DataType.ARRAY_UB)
fromDt istype DataType.UWORD && toDt istype DataType.UWORD -> InferredTypes.knownFor(DataType.ARRAY_UW)
fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
fromDt istype DataType.STR_S && toDt istype DataType.STR_S -> InferredTypes.knownFor(DataType.STR_S)
fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W)
fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B)
else -> InferredTypes.knownFor(DataType.ARRAY_UB)
else -> {
val fdt = fromDt.typeOrElse(DataType.STRUCT)
val tdt = toDt.typeOrElse(DataType.STRUCT)
if(fdt largerThan tdt)
InferredTypes.knownFor(ElementArrayTypes.getValue(fdt))
else
InferredTypes.knownFor(ElementArrayTypes.getValue(tdt))
}
}
}
override fun toString(): String {
@ -548,9 +676,9 @@ class RangeExpr(var from: Expression,
val fromString = from as? StringLiteralValue
val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) {
// string range -> int range over petscii values
fromVal = Petscii.encodePetscii(fromString.value, true)[0].toInt()
toVal = Petscii.encodePetscii(toString.value, true)[0].toInt()
// string range -> int range over character values
fromVal = CompilationTarget.instance.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = CompilationTarget.instance.encodeString(toString.value, fromString.altEncoding)[0].toInt()
} else {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
@ -580,24 +708,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 constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
override fun toString(): String {
return "RegisterExpr(register=$register, pos=$position)"
}
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
}
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
@ -610,10 +720,17 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun targetVarDecl(namespace: INameScope): VarDecl? = targetStatement(namespace) as? VarDecl
fun targetSubroutine(namespace: INameScope): Subroutine? = targetStatement(namespace) as? Subroutine
override fun equals(other: Any?) = other is IdentifierReference && other.nameInSource==nameInSource
override fun hashCode() = nameInSource.hashCode()
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun constValue(program: Program): NumericLiteralValue? {
val node = program.namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolError(this)
@ -630,16 +747,17 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
return "IdentifierRef($nameInSource)"
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String): Boolean =
nameInSource.size==scopedName.size && nameInSource.toTypedArray().contentEquals(scopedName)
override fun inferType(program: Program): InferredTypes.InferredType {
val targetStmt = targetStatement(program.namespace)
if(targetStmt is VarDecl) {
return InferredTypes.knownFor(targetStmt.datatype)
} else {
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
return when (val targetStmt = targetStatement(program.namespace)) {
is VarDecl -> InferredTypes.knownFor(targetStmt.datatype)
is StructDecl -> InferredTypes.knownFor(DataType.STRUCT)
else -> InferredTypes.InferredType.unknown()
}
}
@ -650,22 +768,47 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
return when (value) {
is IdentifierReference -> value.heapId(namespace)
is StringLiteralValue -> value.heapId ?: throw FatalAstException("string is not on the heap: $value")
is ArrayLiteralValue -> value.heapId ?: throw FatalAstException("array is not on the heap: $value")
is StringLiteralValue -> value.heapId
is ArrayLiteralValue -> value.heapId
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)!!
if(decl.datatype!=DataType.STRUCT)
return null
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,
override var arglist: MutableList<Expression>,
override var args: MutableList<Expression>,
override val position: Position) : Expression(), IFunctionCall {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
arglist.forEach { it.linkParents(this) }
args.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target=replacement as IdentifierReference
else {
val idx = args.indexOfFirst { it===node }
args[idx] = replacement as Expression
}
replacement.parent = this
}
override fun constValue(program: Program) = constValue(program, true)
@ -680,8 +823,8 @@ class FunctionCall(override var target: IdentifierReference,
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null)
resultValue = exprfunc(arglist, position, program)
else if(func.returntype==null)
resultValue = exprfunc(args, position, program)
else if(func.known_returntype==null)
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
}
@ -698,15 +841,20 @@ class FunctionCall(override var target: IdentifierReference,
// const-evaluating the builtin function call failed.
return null
}
catch(x: CannotEvaluateException) {
// const-evaluating the builtin function call failed.
return null
}
}
override fun toString(): String {
return "FunctionCall(target=$target, pos=$position)"
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || arglist.any{it.referencesIdentifiers(*name)}
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifier(vararg scopedName: String): Boolean = target.referencesIdentifier(*scopedName) || args.any{it.referencesIdentifier(*scopedName)}
override fun inferType(program: Program): InferredTypes.InferredType {
val constVal = constValue(program ,false)
@ -719,13 +867,19 @@ class FunctionCall(override var target: IdentifierReference,
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return InferredTypes.void() // these have no return value
}
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program)
return builtinFunctionReturnType(target.nameInSource[0], this.args, program)
}
is Subroutine -> {
if(stmt.returntypes.isEmpty())
return InferredTypes.void() // no return value
if(stmt.returntypes.size==1)
return InferredTypes.knownFor(stmt.returntypes[0])
// multiple return values. Can occur for asmsub routines. If there is exactly one register return value, take that.
val numRegisterReturns = stmt.asmReturnvaluesRegisters.count { it.registerOrPair!=null }
if(numRegisterReturns==1)
return InferredTypes.InferredType.known(DataType.UBYTE)
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
}
else -> return InferredTypes.unknown()

View File

@ -1,7 +1,7 @@
package prog8.ast.expressions
import java.util.Objects
import prog8.ast.base.DataType
import java.util.*
object InferredTypes {
@ -35,6 +35,13 @@ object InferredTypes {
}
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
infix fun isAssignableTo(targetDt: InferredType): Boolean =
isKnown && targetDt.isKnown && (datatype!! isAssignableTo targetDt.datatype!!)
infix fun isAssignableTo(targetDt: DataType): Boolean =
isKnown && (datatype!! isAssignableTo targetDt)
infix fun isNotAssignableTo(targetDt: InferredType): Boolean = !this.isAssignableTo(targetDt)
infix fun isNotAssignableTo(targetDt: DataType): Boolean = !this.isAssignableTo(targetDt)
}
private val unknownInstance = InferredType.unknown()
@ -46,7 +53,6 @@ object InferredTypes {
DataType.WORD to InferredType.known(DataType.WORD),
DataType.FLOAT to InferredType.known(DataType.FLOAT),
DataType.STR to InferredType.known(DataType.STR),
DataType.STR_S to InferredType.known(DataType.STR_S),
DataType.ARRAY_UB to InferredType.known(DataType.ARRAY_UB),
DataType.ARRAY_B to InferredType.known(DataType.ARRAY_B),
DataType.ARRAY_UW to InferredType.known(DataType.ARRAY_UW),

File diff suppressed because it is too large Load Diff

View File

@ -1,113 +1,99 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
private var blocks = mutableMapOf<String, Block>()
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
internal fun result(): List<AstException> {
return checkResult
}
private fun nameError(name: String, position: Position, existing: Statement) {
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
}
override fun visit(module: Module) {
vardeclsToAdd.clear()
blocks.clear() // blocks may be redefined within a different module
super.visit(module)
// add any new vardecls to the various scopes
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
}
override fun visit(block: Block): Statement {
override fun visit(block: Block) {
if(block.name in CompilationTarget.instance.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
val existing = blocks[block.name]
if(existing!=null)
nameError(block.name, block.position, existing)
else
blocks[block.name] = block
return super.visit(block)
super.visit(block)
}
override fun visit(functionCall: FunctionCall): Expression {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position)
typecast.linkParents(functionCall.parent)
return super.visit(typecast)
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)
}
return super.visit(functionCall)
super.visit(directive)
}
override fun visit(decl: VarDecl): Statement {
// first, check if there are datatype errors on the vardecl
decl.datatypeErrors.forEach { checkResult.add(it) }
override fun visit(decl: VarDecl) {
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
// now check the identifier
if(decl.name in BuiltinFunctions)
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in AssemblyProgram.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position))
if(decl.name in CompilationTarget.instance.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT) {
if(decl.structHasBeenFlattened)
if (decl.structHasBeenFlattened)
return super.visit(decl) // don't do this multiple times
if(decl.struct==null) {
checkResult.add(NameError("undefined struct type", decl.position))
if (decl.struct == null) {
errors.err("undefined struct type", decl.position)
return super.visit(decl)
}
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes })
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
if(decl.value is NumericLiteralValue) {
checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position))
if (decl.value is NumericLiteralValue) {
errors.err("you cannot initialize a struct using a single value", decl.position)
return super.visit(decl)
}
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
result.linkParents(decl.parent)
return result
if (decl.value != null && decl.value !is ArrayLiteralValue) {
errors.err("initializing a struct requires array literal value", decl.value?.position ?: decl.position)
return super.visit(decl)
}
}
val existing = program.namespace.lookup(listOf(decl.name), decl)
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
return super.visit(decl)
if(decl.definingBlock().name==decl.name)
nameError(decl.name, decl.position, decl.definingBlock())
if(decl.definingSubroutine()?.name==decl.name)
nameError(decl.name, decl.position, decl.definingSubroutine()!!)
super.visit(decl)
}
override fun visit(subroutine: Subroutine): Statement {
if(subroutine.name in AssemblyProgram.opcodeNames) {
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position))
override fun visit(subroutine: Subroutine) {
if(subroutine.name in CompilationTarget.instance.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
errors.err("builtin function cannot be redefined", subroutine.position)
} else {
// already reported elsewhere:
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
@ -117,14 +103,6 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing)
// does the parameter redefine a variable declared elsewhere?
for(param in subroutine.parameters) {
val existingVar = subroutine.lookup(listOf(param.name), subroutine)
if (existingVar != null && existingVar.parent !== subroutine) {
nameError(param.name, param.position, existingVar)
}
}
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
@ -139,289 +117,50 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
nameError(name, sub.position, subroutine)
}
// inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters)
// NOTE:
// - numeric types BYTE and WORD and FLOAT are passed by value;
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty()) {
subroutine.parameters
.filter { it.name !in namesInSub }
.forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.NOT_IN_ZEROPAGE, null, it.name, null, null,
isArray = false, autogeneratedDontRemove = true, position = subroutine.position)
vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl)
}
}
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position))
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
}
}
return super.visit(subroutine)
super.visit(subroutine)
}
override fun visit(label: Label): Statement {
if(label.name in AssemblyProgram.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${label.name}'", label.position))
override fun visit(label: Label) {
if(label.name in CompilationTarget.instance.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position))
errors.err("builtin function cannot be redefined", label.position)
} else {
val existing = program.namespace.lookup(listOf(label.name), label)
if (existing != null && existing !== label)
nameError(label.name, label.position, existing)
}
return super.visit(label)
}
override fun visit(forLoop: ForLoop): Statement {
// If the for loop has a decltype, it means to declare the loopvar inside the loop body
// rather than reusing an already declared loopvar from an outer scope.
// For loops that loop over an interable variable (instead of a range of numbers) get an
// additional interation count variable in their scope.
if(forLoop.loopRegister!=null) {
if(forLoop.loopRegister == Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
} else {
val loopVar = forLoop.loopVar
if (loopVar != null) {
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
val loopvarName = "prog8_loopvar_$validName"
if (forLoop.iterable !is RangeExpr) {
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first())
if (existing == null) {
// create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, null, null,
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
}
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
for(el in existing) {
if(el === label || el.name != label.name)
continue
else {
nameError(label.name, label.position, el)
break
}
}
}
return super.visit(forLoop)
super.visit(label)
}
override fun visit(assignTarget: AssignTarget): AssignTarget {
if(assignTarget.register== Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
return super.visit(assignTarget)
override fun visit(string: StringLiteralValue) {
if (string.value.length > 255)
errors.err("string literal length max is 255", string.position)
super.visit(string)
}
override fun visit(returnStmt: Return): Statement {
if(returnStmt.value!=null) {
// possibly adjust any literal values returned, into the desired returning data type
val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size!=1)
return returnStmt // mismatch in number of return values, error will be printed later.
val lval = returnStmt.value as? NumericLiteralValue
returnStmt.value = lval?.cast(subroutine.returntypes.single()) ?: returnStmt.value!!
}
return super.visit(returnStmt)
}
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
val vardecl = array.parent as? VarDecl
return when {
vardecl!=null -> fixupArrayDatatype(array, vardecl, program)
array.heapId!=null -> {
// fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
// (we don't know the desired datatype here exactly so we guess)
val datatype = determineArrayDt(array.value)
val litval2 = array.cast(datatype)!!
litval2.parent = array.parent
// finally, replace the literal array by a identifier reference.
makeIdentifierFromRefLv(litval2)
}
else -> array
}
}
return array
}
override fun visit(stringLiteral: StringLiteralValue): Expression {
val string = super.visit(stringLiteral)
if(string is StringLiteralValue) {
val vardecl = string.parent as? VarDecl
// intern the string; move it into the heap
if (string.value.length !in 1..255)
checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position))
else {
string.addToHeap()
}
return if (vardecl != null)
string
else
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
}
return string
}
private fun determineArrayDt(array: Array<Expression>): DataType {
val datatypesInArray = array.map { it.inferType(program) }
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when {
DataType.FLOAT in dts -> DataType.ARRAY_F
DataType.WORD in dts -> DataType.ARRAY_W
DataType.UWORD in dts -> DataType.ARRAY_UW
DataType.BYTE in dts -> DataType.ARRAY_B
DataType.UBYTE in dts -> DataType.ARRAY_UB
else -> throw IllegalArgumentException("can't determine type of array")
}
}
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable
// we need to introduce an auto-generated variable for this to be able to refer to the value
// note: if the var references the same literal value, it is not yet de-duplicated here.
array.addToHeap()
val scope = array.definingScope()
val variable = VarDecl.createAuto(array)
return replaceWithIdentifier(variable, scope, array.parent)
}
private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable
// we need to introduce an auto-generated variable for this to be able to refer to the value
// note: if the var references the same literal value, it is not yet de-duplicated here.
string.addToHeap()
val scope = string.definingScope()
val variable = VarDecl.createAuto(string)
return replaceWithIdentifier(variable, scope, string.parent)
}
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
val variable1 = addVarDecl(scope, variable)
// replace the reference literal by a identifier reference
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
identifier.parent = parent
return identifier
}
override fun visit(structDecl: StructDecl): Statement {
override fun visit(structDecl: StructDecl) {
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl!=null && decl.datatype !in NumericDatatypes)
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
errors.err("structs can only contain numerical types", decl.position)
}
return super.visit(structDecl)
super.visit(structDecl)
}
override fun visit(expr: BinaryExpression): Expression {
return when {
expr.left is StringLiteralValue ->
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
expr.right is StringLiteralValue ->
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
else -> super.visit(expr)
}
}
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
val constvalue = operand.constValue(program)
if(constvalue!=null) {
if (expr.operator == "*") {
// repeat a string a number of times
val idt = string.inferType(program)
return StringLiteralValue(idt.typeOrElse(DataType.STR),
string.value.repeat(constvalue.number.toInt()), expr.position)
}
}
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
val idt = string.inferType(program)
return StringLiteralValue(idt.typeOrElse(DataType.STR),
"${string.value}${operand.value}", expr.position)
}
return expr
}
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
val existing = declList.singleOrNull { it.name==variable.name }
return if(existing!=null) {
existing
} else {
declList.add(variable)
variable
}
}
}
internal fun fixupArrayDatatype(array: ArrayLiteralValue, program: Program): ArrayLiteralValue {
val dts = array.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
if(dts.any { it !in NumericDatatypes }) {
return array
}
val dt = when {
DataType.FLOAT in dts -> DataType.ARRAY_F
DataType.WORD in dts -> DataType.ARRAY_W
DataType.UWORD in dts -> DataType.ARRAY_UW
DataType.BYTE in dts -> DataType.ARRAY_B
else -> DataType.ARRAY_UB
}
if(dt==array.type)
return array
// convert values and array type
val elementType = ArrayElementTypes.getValue(dt)
val values = array.value.map { (it as NumericLiteralValue).cast(elementType) as Expression}.toTypedArray()
val array2 = ArrayLiteralValue(dt, values, array.heapId, array.position)
array2.linkParents(array.parent)
return array2
}
internal fun fixupArrayDatatype(array: ArrayLiteralValue, vardecl: VarDecl, program: Program): ArrayLiteralValue {
if(array.heapId!=null) {
val arrayDt = array.type
if(arrayDt!=vardecl.datatype) {
// fix the datatype of the array (also on the heap) to match the vardecl
val litval2 =
try {
val result = array.cast(vardecl.datatype)
if(result==null) {
val constElements = array.value.mapNotNull { it.constValue(program) }
val elementDts = constElements.map { it.type }
if(DataType.FLOAT in elementDts) {
array.cast(DataType.ARRAY_F) ?: ArrayLiteralValue(DataType.ARRAY_F, array.value, array.heapId, array.position)
} else {
val numbers = constElements.map { it.number.toInt() }
val minValue = numbers.min()!!
val maxValue = numbers.max()!!
if (minValue >= 0) {
// only positive values, so uword or ubyte
val dt = if(maxValue<256) DataType.ARRAY_UB else DataType.ARRAY_UW
array.cast(dt) ?: ArrayLiteralValue(dt, array.value, array.heapId, array.position)
} else {
// negative value present, so word or byte
val dt = if(minValue >= -128 && maxValue<=127) DataType.ARRAY_B else DataType.ARRAY_W
array.cast(dt) ?: ArrayLiteralValue(dt, array.value, array.heapId, array.position)
}
}
}
else result
} catch(x: ExpressionError) {
// couldn't cast permanently.
// instead, simply adjust the array type and trust the AstChecker to report the exact error
ArrayLiteralValue(vardecl.datatype, array.value, array.heapId, array.position)
}
vardecl.value = litval2
litval2.linkParents(vardecl)
litval2.addToHeap()
return litval2
}
}
return array
}

View File

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

View File

@ -0,0 +1,105 @@
package prog8.ast.processing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
return listOf(IAstModification.ReplaceNode(
decl, result, parent
))
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// For non-kernel subroutines and non-asm parameters:
// inject subroutine params as local variables (if they're not there yet).
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty() && subroutine.parameters.isNotEmpty()) {
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
if(!vars.containsAll(subroutine.parameters.map{it.name})) {
return subroutine.parameters
.filter { it.name !in namesInSub }
.map {
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
IAstModification.InsertFirst(vardecl, subroutine)
}
}
}
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftStr = expr.left as? StringLiteralValue
val rightStr = expr.right as? StringLiteralValue
if(expr.operator == "+") {
val concatenatedString = concatString(expr)
if(concatenatedString!=null)
return listOf(IAstModification.ReplaceNode(expr, concatenatedString, parent))
}
else if(expr.operator == "*") {
if (leftStr!=null) {
val amount = expr.right.constValue(program)
if(amount!=null) {
val string = leftStr.value.repeat(amount.number.toInt())
val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
}
}
else if (rightStr!=null) {
val amount = expr.right.constValue(program)
if(amount!=null) {
val string = rightStr.value.repeat(amount.number.toInt())
val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, strval, parent))
}
}
}
return noModifications
}
private fun concatString(expr: BinaryExpression): StringLiteralValue? {
val rightStrval = expr.right as? StringLiteralValue
val leftStrval = expr.left as? StringLiteralValue
return when {
expr.operator!="+" -> null
expr.left is BinaryExpression && rightStrval!=null -> {
val subStrVal = concatString(expr.left as BinaryExpression)
if(subStrVal==null)
null
else
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position)
}
expr.right is BinaryExpression && leftStrval!=null -> {
val subStrVal = concatString(expr.right as BinaryExpression)
if(subStrVal==null)
null
else
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position)
}
leftStrval!=null && rightStrval!=null -> {
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position)
}
else -> null
}
}
}

View File

@ -0,0 +1,424 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
interface IAstModification {
fun perform()
class Remove(val node: Node, val parent: INameScope) : IAstModification {
override fun perform() {
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
}
}
class SetExpression(private val setter: (newExpr: Expression) -> Unit, private val newExpr: Expression, private val parent: Node) : IAstModification {
override fun perform() {
setter(newExpr)
newExpr.linkParents(parent)
}
}
class InsertFirst(private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
parent.statements.add(0, stmt)
stmt.linkParents(parent as Node)
}
}
class InsertLast(private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
parent.statements.add(stmt)
stmt.linkParents(parent as Node)
}
}
class InsertAfter(private val after: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
val idx = parent.statements.indexOfFirst { it===after } + 1
parent.statements.add(idx, stmt)
stmt.linkParents(parent as Node)
}
}
class InsertBefore(private val before: Statement, private val stmt: Statement, private val parent: INameScope) : IAstModification {
override fun perform() {
val idx = parent.statements.indexOfFirst { it===before }
parent.statements.add(idx, stmt)
stmt.linkParents(parent as Node)
}
}
class ReplaceNode(private val node: Node, private val replacement: Node, private val parent: Node) : IAstModification {
override fun perform() {
parent.replaceChildNode(node, replacement)
replacement.linkParents(parent)
}
}
class SwapOperands(private val expr: BinaryExpression): IAstModification {
override fun perform() {
require(expr.operator in associativeOperators)
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
}
}
}
abstract class AstWalker {
open fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(block: Block, 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(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(label: Label, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(module: Module, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(numLiteral: NumericLiteralValue, 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(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(block: Block, 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(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(label: Label, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(module: Module, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(numLiteral: NumericLiteralValue, 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(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(untilLoop: UntilLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = emptyList()
private val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
private fun track(mods: Iterable<IAstModification>, node: Node, parent: Node) {
for (it in mods) modifications += Triple(it, node, parent)
}
fun applyModifications(): Int {
modifications.forEach {
it.first.perform()
}
val amount = modifications.size
modifications.clear()
return amount
}
fun visit(program: Program) {
track(before(program, program), program, program)
program.modules.forEach { it.accept(this, program) }
track(after(program, program), program, program)
}
fun visit(module: Module, parent: Node) {
track(before(module, parent), module, parent)
module.statements.forEach{ it.accept(this, module) }
track(after(module, parent), module, parent)
}
fun visit(expr: PrefixExpression, parent: Node) {
track(before(expr, parent), expr, parent)
expr.expression.accept(this, expr)
track(after(expr, parent), expr, parent)
}
fun visit(expr: BinaryExpression, parent: Node) {
track(before(expr, parent), expr, parent)
expr.left.accept(this, expr)
expr.right.accept(this, expr)
track(after(expr, parent), expr, parent)
}
fun visit(directive: Directive, parent: Node) {
track(before(directive, parent), directive, parent)
track(after(directive, parent), directive, parent)
}
fun visit(block: Block, parent: Node) {
track(before(block, parent), block, parent)
block.statements.forEach { it.accept(this, block) }
track(after(block, parent), block, parent)
}
fun visit(decl: VarDecl, parent: Node) {
track(before(decl, parent), decl, parent)
decl.value?.accept(this, decl)
decl.arraysize?.accept(this, decl)
decl.struct?.accept(this, decl)
track(after(decl, parent), decl, parent)
}
fun visit(subroutine: Subroutine, parent: Node) {
track(before(subroutine, parent), subroutine, parent)
subroutine.statements.forEach { it.accept(this, subroutine) }
track(after(subroutine, parent), subroutine, parent)
}
fun visit(functionCall: FunctionCall, parent: Node) {
track(before(functionCall, parent), functionCall, parent)
functionCall.target.accept(this, functionCall)
functionCall.args.forEach { it.accept(this, functionCall) }
track(after(functionCall, parent), functionCall, parent)
}
fun visit(functionCallStatement: FunctionCallStatement, parent: Node) {
track(before(functionCallStatement, parent), functionCallStatement, parent)
functionCallStatement.target.accept(this, functionCallStatement)
functionCallStatement.args.forEach { it.accept(this, functionCallStatement) }
track(after(functionCallStatement, parent), functionCallStatement, parent)
}
fun visit(identifier: IdentifierReference, parent: Node) {
track(before(identifier, parent), identifier, parent)
track(after(identifier, parent), identifier, parent)
}
fun visit(jump: Jump, parent: Node) {
track(before(jump, parent), jump, parent)
jump.identifier?.accept(this, jump)
track(after(jump, parent), jump, parent)
}
fun visit(ifStatement: IfStatement, parent: Node) {
track(before(ifStatement, parent), ifStatement, parent)
ifStatement.condition.accept(this, ifStatement)
ifStatement.truepart.accept(this, ifStatement)
ifStatement.elsepart.accept(this, ifStatement)
track(after(ifStatement, parent), ifStatement, parent)
}
fun visit(branchStatement: BranchStatement, parent: Node) {
track(before(branchStatement, parent), branchStatement, parent)
branchStatement.truepart.accept(this, branchStatement)
branchStatement.elsepart.accept(this, branchStatement)
track(after(branchStatement, parent), branchStatement, parent)
}
fun visit(range: RangeExpr, parent: Node) {
track(before(range, parent), range, parent)
range.from.accept(this, range)
range.to.accept(this, range)
range.step.accept(this, range)
track(after(range, parent), range, parent)
}
fun visit(label: Label, parent: Node) {
track(before(label, parent), label, parent)
track(after(label, parent), label, parent)
}
fun visit(numLiteral: NumericLiteralValue, parent: Node) {
track(before(numLiteral, parent), numLiteral, parent)
track(after(numLiteral, parent), numLiteral, parent)
}
fun visit(string: StringLiteralValue, parent: Node) {
track(before(string, parent), string, parent)
track(after(string, parent), string, parent)
}
fun visit(array: ArrayLiteralValue, parent: Node) {
track(before(array, parent), array, parent)
array.value.forEach { v->v.accept(this, array) }
track(after(array, parent), array, parent)
}
fun visit(assignment: Assignment, parent: Node) {
track(before(assignment, parent), assignment, parent)
assignment.target.accept(this, assignment)
assignment.value.accept(this, assignment)
track(after(assignment, parent), assignment, parent)
}
fun visit(postIncrDecr: PostIncrDecr, parent: Node) {
track(before(postIncrDecr, parent), postIncrDecr, parent)
postIncrDecr.target.accept(this, postIncrDecr)
track(after(postIncrDecr, parent), postIncrDecr, parent)
}
fun visit(breakStmt: Break, parent: Node) {
track(before(breakStmt, parent), breakStmt, parent)
track(after(breakStmt, parent), breakStmt, parent)
}
fun visit(forLoop: ForLoop, parent: Node) {
track(before(forLoop, parent), forLoop, parent)
forLoop.loopVar.accept(this, forLoop)
forLoop.iterable.accept(this, forLoop)
forLoop.body.accept(this, forLoop)
track(after(forLoop, parent), forLoop, parent)
}
fun visit(whileLoop: WhileLoop, parent: Node) {
track(before(whileLoop, parent), whileLoop, parent)
whileLoop.condition.accept(this, whileLoop)
whileLoop.body.accept(this, whileLoop)
track(after(whileLoop, parent), whileLoop, parent)
}
fun visit(repeatLoop: RepeatLoop, parent: Node) {
track(before(repeatLoop, parent), repeatLoop, parent)
repeatLoop.iterations?.accept(this, repeatLoop)
repeatLoop.body.accept(this, repeatLoop)
track(after(repeatLoop, parent), repeatLoop, parent)
}
fun visit(untilLoop: UntilLoop, parent: Node) {
track(before(untilLoop, parent), untilLoop, parent)
untilLoop.condition.accept(this, untilLoop)
untilLoop.body.accept(this, untilLoop)
track(after(untilLoop, parent), untilLoop, parent)
}
fun visit(returnStmt: Return, parent: Node) {
track(before(returnStmt, parent), returnStmt, parent)
returnStmt.value?.accept(this, returnStmt)
track(after(returnStmt, parent), returnStmt, parent)
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
arrayIndexedExpression.arrayvar.accept(this, arrayIndexedExpression)
arrayIndexedExpression.indexer.accept(this, arrayIndexedExpression)
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
}
fun visit(assignTarget: AssignTarget, parent: Node) {
track(before(assignTarget, parent), assignTarget, parent)
assignTarget.arrayindexed?.accept(this, assignTarget)
assignTarget.identifier?.accept(this, assignTarget)
assignTarget.memoryAddress?.accept(this, assignTarget)
track(after(assignTarget, parent), assignTarget, parent)
}
fun visit(scope: AnonymousScope, parent: Node) {
track(before(scope, parent), scope, parent)
scope.statements.forEach { it.accept(this, scope) }
track(after(scope, parent), scope, parent)
}
fun visit(typecast: TypecastExpression, parent: Node) {
track(before(typecast, parent), typecast, parent)
typecast.expression.accept(this, typecast)
track(after(typecast, parent), typecast, parent)
}
fun visit(memread: DirectMemoryRead, parent: Node) {
track(before(memread, parent), memread, parent)
memread.addressExpression.accept(this, memread)
track(after(memread, parent), memread, parent)
}
fun visit(memwrite: DirectMemoryWrite, parent: Node) {
track(before(memwrite, parent), memwrite, parent)
memwrite.addressExpression.accept(this, memwrite)
track(after(memwrite, parent), memwrite, parent)
}
fun visit(addressOf: AddressOf, parent: Node) {
track(before(addressOf, parent), addressOf, parent)
addressOf.identifier.accept(this, addressOf)
track(after(addressOf, parent), addressOf, parent)
}
fun visit(inlineAssembly: InlineAssembly, parent: Node) {
track(before(inlineAssembly, parent), inlineAssembly, parent)
track(after(inlineAssembly, parent), inlineAssembly, parent)
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) {
track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
}
fun visit(nopStatement: NopStatement, parent: Node) {
track(before(nopStatement, parent), nopStatement, parent)
track(after(nopStatement, parent), nopStatement, parent)
}
fun visit(whenStatement: WhenStatement, parent: Node) {
track(before(whenStatement, parent), whenStatement, parent)
whenStatement.condition.accept(this, whenStatement)
whenStatement.choices.forEach { it.accept(this, whenStatement) }
track(after(whenStatement, parent), whenStatement, parent)
}
fun visit(whenChoice: WhenChoice, parent: Node) {
track(before(whenChoice, parent), whenChoice, parent)
whenChoice.values?.forEach { it.accept(this, whenChoice) }
whenChoice.statements.accept(this, whenChoice)
track(after(whenChoice, parent), whenChoice, parent)
}
fun visit(structDecl: StructDecl, parent: Node) {
track(before(structDecl, parent), structDecl, parent)
structDecl.statements.forEach { it.accept(this, structDecl) }
track(after(structDecl, parent), structDecl, parent)
}
}

View File

@ -1,261 +0,0 @@
package prog8.ast.processing
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
interface IAstModifyingVisitor {
fun visit(program: Program) {
program.modules.forEach { visit(it) }
}
fun visit(module: Module) {
module.statements = module.statements.map { it.accept(this) }.toMutableList()
}
fun visit(expr: PrefixExpression): Expression {
expr.expression = expr.expression.accept(this)
return expr
}
fun visit(expr: BinaryExpression): Expression {
expr.left = expr.left.accept(this)
expr.right = expr.right.accept(this)
return expr
}
fun visit(directive: Directive): Statement {
return directive
}
fun visit(block: Block): Statement {
block.statements = block.statements.map { it.accept(this) }.toMutableList()
return block
}
fun visit(decl: VarDecl): Statement {
decl.value = decl.value?.accept(this)
decl.arraysize?.accept(this)
return decl
}
fun visit(subroutine: Subroutine): Statement {
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
return subroutine
}
fun visit(functionCall: FunctionCall): Expression {
val newtarget = functionCall.target.accept(this)
if(newtarget is IdentifierReference)
functionCall.target = newtarget
else
throw FatalAstException("cannot change class of function call target")
functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList()
return functionCall
}
fun visit(functionCallStatement: FunctionCallStatement): Statement {
val newtarget = functionCallStatement.target.accept(this)
if(newtarget is IdentifierReference)
functionCallStatement.target = newtarget
else
throw FatalAstException("cannot change class of function call target")
functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList()
return functionCallStatement
}
fun visit(identifier: IdentifierReference): Expression {
// note: this is an identifier that is used in an expression.
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
return identifier
}
fun visit(jump: Jump): Statement {
if(jump.identifier!=null) {
val ident = jump.identifier.accept(this)
if(ident is IdentifierReference && ident!==jump.identifier) {
return Jump(null, ident, null, jump.position)
}
}
return jump
}
fun visit(ifStatement: IfStatement): Statement {
ifStatement.condition = ifStatement.condition.accept(this)
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
return ifStatement
}
fun visit(branchStatement: BranchStatement): Statement {
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
return branchStatement
}
fun visit(range: RangeExpr): Expression {
range.from = range.from.accept(this)
range.to = range.to.accept(this)
range.step = range.step.accept(this)
return range
}
fun visit(label: Label): Statement {
return label
}
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
return literalValue
}
fun visit(stringLiteral: StringLiteralValue): Expression {
return stringLiteral
}
fun visit(arrayLiteral: ArrayLiteralValue): Expression {
for(av in arrayLiteral.value.withIndex()) {
val newvalue = av.value.accept(this)
arrayLiteral.value[av.index] = newvalue
}
return arrayLiteral
}
fun visit(assignment: Assignment): Statement {
assignment.target = assignment.target.accept(this)
assignment.value = assignment.value.accept(this)
return assignment
}
fun visit(postIncrDecr: PostIncrDecr): Statement {
postIncrDecr.target = postIncrDecr.target.accept(this)
return postIncrDecr
}
fun visit(contStmt: Continue): Statement {
return contStmt
}
fun visit(breakStmt: Break): Statement {
return breakStmt
}
fun visit(forLoop: ForLoop): Statement {
when(val newloopvar = forLoop.loopVar?.accept(this)) {
is IdentifierReference -> forLoop.loopVar = newloopvar
null -> forLoop.loopVar = null
else -> throw FatalAstException("can't change class of loopvar")
}
forLoop.iterable = forLoop.iterable.accept(this)
forLoop.body = forLoop.body.accept(this) as AnonymousScope
return forLoop
}
fun visit(whileLoop: WhileLoop): Statement {
whileLoop.condition = whileLoop.condition.accept(this)
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
return whileLoop
}
fun visit(repeatLoop: RepeatLoop): Statement {
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
return repeatLoop
}
fun visit(returnStmt: Return): Statement {
returnStmt.value = returnStmt.value?.accept(this)
return returnStmt
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
val ident = arrayIndexedExpression.identifier.accept(this)
if(ident is IdentifierReference)
arrayIndexedExpression.identifier = ident
arrayIndexedExpression.arrayspec.accept(this)
return arrayIndexedExpression
}
fun visit(assignTarget: AssignTarget): AssignTarget {
when (val ident = assignTarget.identifier?.accept(this)) {
is IdentifierReference -> assignTarget.identifier = ident
null -> assignTarget.identifier = null
else -> throw FatalAstException("can't change class of assign target identifier")
}
assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this)
assignTarget.memoryAddress?.let { visit(it) }
return assignTarget
}
fun visit(scope: AnonymousScope): Statement {
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
return scope
}
fun visit(typecast: TypecastExpression): Expression {
typecast.expression = typecast.expression.accept(this)
return typecast
}
fun visit(memread: DirectMemoryRead): Expression {
memread.addressExpression = memread.addressExpression.accept(this)
return memread
}
fun visit(memwrite: DirectMemoryWrite) {
memwrite.addressExpression = memwrite.addressExpression.accept(this)
}
fun visit(addressOf: AddressOf): Expression {
val ident = addressOf.identifier.accept(this)
if(ident is IdentifierReference)
addressOf.identifier = ident
else
throw FatalAstException("can't change class of addressof identifier")
return addressOf
}
fun visit(inlineAssembly: InlineAssembly): Statement {
return inlineAssembly
}
fun visit(registerExpr: RegisterExpr): Expression {
return registerExpr
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
return builtinFunctionStatementPlaceholder
}
fun visit(nopStatement: NopStatement): Statement {
return nopStatement
}
fun visit(whenStatement: WhenStatement): Statement {
whenStatement.condition = whenStatement.condition.accept(this)
whenStatement.choices.forEach { it.accept(this) }
return whenStatement
}
fun visit(whenChoice: WhenChoice) {
whenChoice.values = whenChoice.values?.map { it.accept(this) }
val stmt = whenChoice.statements.accept(this)
if(stmt is AnonymousScope)
whenChoice.statements = stmt
else {
whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position)
whenChoice.statements.linkParents(whenChoice)
}
}
fun visit(structDecl: StructDecl): Statement {
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
return structDecl
}
fun visit(structLv: StructLiteralValue): Expression {
structLv.values = structLv.values.map { it.accept(this) }
return structLv
}
}

View File

@ -7,7 +7,7 @@ import prog8.ast.statements.*
interface IAstVisitor {
fun visit(program: Program) {
program.modules.forEach { visit(it) }
program.modules.forEach { it.accept(this) }
}
fun visit(module: Module) {
@ -33,6 +33,7 @@ interface IAstVisitor {
fun visit(decl: VarDecl) {
decl.value?.accept(this)
decl.arraysize?.accept(this)
decl.struct?.accept(this)
}
fun visit(subroutine: Subroutine) {
@ -41,12 +42,12 @@ interface IAstVisitor {
fun visit(functionCall: FunctionCall) {
functionCall.target.accept(this)
functionCall.arglist.forEach { it.accept(this) }
functionCall.args.forEach { it.accept(this) }
}
fun visit(functionCallStatement: FunctionCallStatement) {
functionCallStatement.target.accept(this)
functionCallStatement.arglist.forEach { it.accept(this) }
functionCallStatement.args.forEach { it.accept(this) }
}
fun visit(identifier: IdentifierReference) {
@ -95,14 +96,11 @@ interface IAstVisitor {
postIncrDecr.target.accept(this)
}
fun visit(contStmt: Continue) {
}
fun visit(breakStmt: Break) {
}
fun visit(forLoop: ForLoop) {
forLoop.loopVar?.accept(this)
forLoop.loopVar.accept(this)
forLoop.iterable.accept(this)
forLoop.body.accept(this)
}
@ -113,17 +111,22 @@ interface IAstVisitor {
}
fun visit(repeatLoop: RepeatLoop) {
repeatLoop.untilCondition.accept(this)
repeatLoop.iterations?.accept(this)
repeatLoop.body.accept(this)
}
fun visit(untilLoop: UntilLoop) {
untilLoop.condition.accept(this)
untilLoop.body.accept(this)
}
fun visit(returnStmt: Return) {
returnStmt.value?.accept(this)
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
arrayIndexedExpression.identifier.accept(this)
arrayIndexedExpression.arrayspec.accept(this)
arrayIndexedExpression.arrayvar.accept(this)
arrayIndexedExpression.indexer.accept(this)
}
fun visit(assignTarget: AssignTarget) {
@ -155,9 +158,6 @@ interface IAstVisitor {
fun visit(inlineAssembly: InlineAssembly) {
}
fun visit(registerExpr: RegisterExpr) {
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
}
@ -177,8 +177,4 @@ interface IAstVisitor {
fun visit(structDecl: StructDecl) {
structDecl.statements.forEach { it.accept(this) }
}
fun visit(structLv: StructLiteralValue) {
structLv.values.forEach { it.accept(this) }
}
}

View File

@ -1,36 +1,22 @@
package prog8.ast.processing
import prog8.ast.Module
import prog8.ast.base.SyntaxError
import prog8.ast.base.printWarning
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.statements.Directive
import prog8.ast.statements.Statement
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
private val checkResult: MutableList<SyntaxError> = mutableListOf()
internal fun result(): List<SyntaxError> {
return checkResult
}
internal class ImportedModuleDirectiveRemover: AstWalker() {
/**
* Most global directives don't apply for imported modules, so remove them
*/
override fun visit(module: Module) {
super.visit(module)
val newStatements : MutableList<Statement> = mutableListOf()
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
for (sourceStmt in module.statements) {
val stmt = sourceStmt.accept(this)
if(stmt is Directive && stmt.parent is Module) {
if(stmt.directive in moduleLevelDirectives) {
printWarning("ignoring module directive because it was imported", stmt.position, stmt.directive)
continue
}
}
newStatements.add(stmt)
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
private val noModifications = emptyList<IAstModification>()
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent as INameScope))
}
module.statements = newStatements
return noModifications
}
}

View File

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

View File

@ -0,0 +1,71 @@
package prog8.ast.processing
/*
This is here for reference only, reflection based ast walking is very slow
when compared to the more verbose visitor pattern interfaces.
Too bad, because the code is very small
*/
//import prog8.ast.NoAstWalk
//import prog8.ast.Node
//import prog8.ast.Program
//import prog8.ast.base.Position
//import prog8.ast.expressions.BinaryExpression
//import prog8.ast.expressions.NumericLiteralValue
//import kotlin.reflect.KClass
//import kotlin.reflect.KVisibility
//import kotlin.reflect.full.declaredMemberProperties
//import kotlin.reflect.full.isSubtypeOf
//import kotlin.reflect.full.starProjectedType
//
//
//class ReflectionAstWalker {
// private val nodeType = Node::class.starProjectedType
// private val collectionType = Collection::class.starProjectedType
//
//
// fun walk(node: Node, nesting: Int) {
// val nodetype: KClass<out Node> = node::class
// val indent = " ".repeat(nesting)
// //println("$indent VISITING ${nodetype.simpleName}")
// val visibleAstMembers = nodetype.declaredMemberProperties.filter {
// it.visibility!=KVisibility.PRIVATE && !it.isLateinit &&
// !(it.annotations.any{a->a is NoAstWalk})
// }
// for(prop in visibleAstMembers) {
// if(prop.returnType.isSubtypeOf(nodeType)) {
// // println("$indent +PROP: ${prop.name}")
// walk(prop.call(node) as Node, nesting + 1)
// }
// else if(prop.returnType.isSubtypeOf(collectionType)) {
// val elementType = prop.returnType.arguments.single().type
// if(elementType!=null && elementType.isSubtypeOf(nodeType)) {
// val nodes = prop.call(node) as Collection<Node>
// nodes.forEach { walk(it, nesting+1) }
// }
// }
// }
// }
// fun walk(program: Program) {
// for(module in program.modules) {
// println("---MODULE $module---")
// walk(module, 0)
// }
// }
//}
//
//
//fun main() {
// val ast = BinaryExpression(
// NumericLiteralValue.optimalInteger(100, Position.DUMMY),
// "+",
// NumericLiteralValue.optimalInteger(200, Position.DUMMY),
// Position.DUMMY
// )
//
// val walker = ReflectionAstWalker()
// walker.walk(ast,0)
//
//}

View File

@ -1,258 +1,364 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
when {
structAssignment.value is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null)
throw FatalAstException("can only assign arrays or structs to structs")
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
null, sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
structAssignment.value is StructLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}
}
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - blocks are ordered by address, where blocks without address are put at the end.
// - in every scope:
// -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first.
// -- all vardecls then follow.
// -- the remaining statements then follow in their original order.
//
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
// - all other subroutines will be moved to the end of their block.
// - library blocks are put last.
// - blocks are ordered by address, where blocks without address are placed last.
// - in every block and module, most directives and vardecls are moved to the top. (not in subroutines!)
// - the 'start' subroutine is moved to the top.
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
// - 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.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val noModifications = emptyList<IAstModification>()
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
private val addReturns = mutableListOf<Pair<INameScope, Int>>()
override fun visit(module: Module) {
addReturns.clear()
super.visit(module)
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything
val nonLibraryBlocks = module.statements.withIndex()
.filter { it.value is Block && !(it.value as Block).isInLibrary }
.map { it.index to it.value }
.reversed()
for(nonLibBlock in nonLibraryBlocks)
module.statements.removeAt(nonLibBlock.first)
for(nonLibBlock in nonLibraryBlocks)
module.statements.add(0, nonLibBlock.second)
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
if(mainBlock!=null && (mainBlock as Block).address==null) {
module.remove(mainBlock)
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
if(mainBlock!=null && mainBlock.address==null) {
module.statements.remove(mainBlock)
module.statements.add(0, mainBlock)
}
val varDecls = module.statements.filterIsInstance<VarDecl>()
module.statements.removeAll(varDecls)
module.statements.addAll(0, varDecls)
reorderVardeclsAndDirectives(module.statements)
return noModifications
}
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
module.statements.removeAll(directives)
module.statements.addAll(0, directives)
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
val varDecls = statements.filterIsInstance<VarDecl>()
statements.removeAll(varDecls)
statements.addAll(0, varDecls)
for(pos in addReturns) {
println(pos)
val returnStmt = Return(null, pos.first.position)
returnStmt.linkParents(pos.first as Node)
pos.first.statements.add(pos.second, returnStmt)
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
statements.removeAll(directives)
statements.addAll(0, directives)
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
parent as Module
if(block.isInLibrary) {
return listOf(
IAstModification.Remove(block, parent),
IAstModification.InsertLast(block, parent)
)
}
reorderVardeclsAndDirectives(block.statements)
return noModifications
}
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.name=="start" && parent is Block) {
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
return listOf(
IAstModification.Remove(subroutine, parent),
IAstModification.InsertFirst(subroutine, parent)
)
}
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
is NumericLiteralValue -> {
arrayIndexedExpression.indexer.indexNum = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is IdentifierReference -> {
arrayIndexedExpression.indexer.indexVar = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is Expression -> {
// replace complex indexing with a temp variable
return getAutoIndexerVarFor(arrayIndexedExpression)
}
else -> return noModifications
}
}
override fun visit(block: Block): Statement {
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val subroutine = expr.definingSubroutine()!!
val statement = expr.containingStatement()
val indexerVarPrefix = "prog8_autovar_index_"
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
val subroutines = block.statements.filterIsInstance<Subroutine>()
var numSubroutinesAtEnd = 0
// move all subroutines to the end of the block
for (subroutine in subroutines) {
if(subroutine.name!="start" || block.name!="main") {
block.remove(subroutine)
block.statements.add(subroutine)
}
numSubroutinesAtEnd++
}
// move the "start" subroutine to the top
if(block.name=="main") {
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
block.remove(it)
block.statements.add(0, it)
numSubroutinesAtEnd--
}
// TODO make this even smarter so that an indexerVar can be reused for a different following statement... requires updating the partOfStatement?
var indexerVar = repo.firstOrNull { it.replaces isSameAs expr.indexer }
if(indexerVar==null) {
// add another loop index var to be used for this expression
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer, statement)
repo.add(indexerVar)
// create the indexer var at block level scope
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
}
indexerVar.used++ // keep track of how many times it it used, to avoid assigning it multiple times
// make sure there is a 'return' in front of the first subroutine
// (if it isn't the first statement in the block itself, and isn't the program's entrypoint)
if(numSubroutinesAtEnd>0 && block.statements.size > (numSubroutinesAtEnd+1)) {
val firstSub = block.statements[block.statements.size - numSubroutinesAtEnd] as Subroutine
if(firstSub.name != "start" && block.name != "main") {
val stmtBeforeFirstSub = block.statements[block.statements.size - numSubroutinesAtEnd - 1]
if (stmtBeforeFirstSub !is Return
&& stmtBeforeFirstSub !is Jump
&& stmtBeforeFirstSub !is Subroutine
&& stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) {
val ret = Return(null, stmtBeforeFirstSub.position)
ret.linkParents(block)
block.statements.add(block.statements.size - numSubroutinesAtEnd, ret)
// replace the indexer with just the variable
// assign the indexing expression to the helper variable, but only if that hasn't been done already
val indexerExpression = expr.indexer.origExpression!!
val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
if(indexerVar.used==1) {
val assign = Assignment(target, indexerExpression, indexerExpression.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
}
modifications.add(IAstModification.SetExpression( {
expr.indexer.indexVar = it as IdentifierReference
expr.indexer.indexNum = null
expr.indexer.origExpression = null
}, target.identifier!!.copy(), expr.indexer))
return modifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
it.first?.first() ?: Int.MAX_VALUE
}
whenStatement.choices.clear()
choices.mapTo(whenStatement.choices) { it.second }
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val declConstValue = declValue.constValue(program)
if(declConstValue==null) {
// move the vardecl (without value) to the scope and replace this with a regular assignment
// Unless we're dealing with a floating point variable because that will actually make things less efficient at the moment (because floats are mostly calcualated via the stack)
if(decl.datatype!=DataType.FLOAT) {
decl.value = null
decl.allowInitializeWithZero = false
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, declValue, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope())
)
}
}
}
val varDecls = block.statements.filterIsInstance<VarDecl>()
block.statements.removeAll(varDecls)
block.statements.addAll(0, varDecls)
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
block.statements.removeAll(directives)
block.statements.addAll(0, directives)
block.linkParents(block.parent)
// create subroutine that initializes the block's variables (if any)
val varInits = block.statements.withIndex().filter { it.value is VariableInitializationAssignment }
if(varInits.isNotEmpty()) {
val statements = varInits.map{it.value}.toMutableList()
val varInitSub = Subroutine(initvarsSubName, emptyList(), emptyList(), emptyList(), emptyList(),
emptySet(), null, false, statements, block.position)
varInitSub.keepAlways = true
varInitSub.linkParents(block)
block.statements.add(varInitSub)
// remove the varinits from the block's statements
for(index in varInits.map{it.index}.reversed())
block.statements.removeAt(index)
}
return super.visit(block)
return noModifications
}
override fun visit(subroutine: Subroutine): Statement {
super.visit(subroutine)
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program)
var assignments = emptyList<Assignment>()
val scope = subroutine.definingScope()
if(scope is Subroutine) {
for(stmt in scope.statements.withIndex()) {
if(stmt.index>0 && stmt.value===subroutine) {
val precedingStmt = scope.statements[stmt.index-1]
if(precedingStmt !is Jump && precedingStmt !is Subroutine) {
// insert a return statement before a nested subroutine, to avoid falling trough inside the subroutine
addReturns.add(Pair(scope, stmt.index))
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
} else {
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
}
}
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
assignments = if (assignment.value is ArrayLiteralValue) {
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
} else {
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
}
}
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
val scope = assignment.definingScope()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
modifications.add(IAstModification.Remove(assignment, scope))
return modifications
}
return noModifications
}
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))
}
}
}
}
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
subroutine.statements.removeAll(varDecls)
subroutine.statements.addAll(0, varDecls)
val directives = subroutine.statements.filter {it is Directive && it.directive in directivesToMove}
subroutine.statements.removeAll(directives)
subroutine.statements.addAll(0, directives)
if(subroutine.returntypes.isEmpty()) {
// 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
if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) {
if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) {
val returnStmt = Return(null, subroutine.position)
returnStmt.linkParents(subroutine)
subroutine.statements.add(returnStmt)
}
}
}
return subroutine
return noModifications
}
override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment)
if(assg !is Assignment)
return assg
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val alv = assign.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
}
// see if a typecast is needed to convert the value's type into the proper target type
val valueItype = assg.value.inferType(program)
val targetItype = assg.target.inferType(program, assg)
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!!
if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position)
return emptyList()
}
val alv = sourceVar.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
}
if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
// struct assignments will be flattened (if it's not a struct literal)
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) {
if (assg.value is StructLiteralValue)
return assg // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed)
val assignments = flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
return if (assignments.isEmpty()) {
// something went wrong (probably incompatible struct types)
// we'll get an error later from the AstChecker
assg
} else {
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
scope.linkParents(assg.parent)
scope
}
}
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
if(targetVar.arraysize==null) {
errors.err("array has no defined size", identifier.position)
return emptyList()
}
if(assg.aug_op!=null) {
// transform augmented assg into normal assg so we have one case less to deal with later
val newTarget: Expression =
when {
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
assg.target.identifier != null -> assg.target.identifier!!
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
else -> throw FatalAstException("strange assg")
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
errors.err("element count mismatch", position)
return emptyList()
}
// TODO use a pointer loop instead of individual assignments
return alv.value.mapIndexed { index, value ->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
Assignment(AssignTarget(null, idx, null, position), value, value.position)
}
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
val slv = structAssignment.value as? ArrayLiteralValue
if(slv==null || slv.value.size != struct.numberOfElements) {
errors.err("element count mismatch", structAssignment.position)
return emptyList()
}
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
targetDecl as VarDecl
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
sourceValue, sourceValue.position)
assign.linkParents(structAssignment)
assign
}
}
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
// TODO use memcopy beyond a certain number of elements
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
when (structAssignment.value) {
is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
when {
sourceVar.struct!=null -> {
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
if(struct.statements.size!=sourceStruct.statements.size)
return listOf() // error will be printed elsewhere
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
expression.linkParents(assg.parent)
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
convertedAssignment.linkParents(assg.parent)
return super.visit(convertedAssignment)
sourceVar.isArray -> {
val array = (sourceVar.value as ArrayLiteralValue).value
if(struct.statements.size!=array.size)
return listOf() // error will be printed elsewhere
return struct.statements.zip(array).map {
val decl = it.first as VarDecl
val mangled = mangledStructMemberName(identifierName, decl.name)
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
val target = AssignTarget(targetName, null, null, structAssignment.position)
val assign = Assignment(target, it.second, structAssignment.position)
assign.linkParents(structAssignment)
assign
}
}
else -> {
throw FatalAstException("can only assign arrays or structs to structs")
}
}
}
is ArrayLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}
return assg
}
}

View File

@ -2,113 +2,167 @@ package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.printWarning
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor {
// Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
// (this includes function call arguments)
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() {
/*
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
* (this includes function call arguments)
*/
override fun visit(expr: BinaryExpression): Expression {
val expr2 = super.visit(expr)
if(expr2 !is BinaryExpression)
return expr2
val leftDt = expr2.left.inferType(program)
val rightDt = expr2.right.inferType(program)
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)) {
// don't add a typecast on an array initializer value
if(valueDt.typeOrElse(DataType.STRUCT) in IntegerDatatypes && decl.datatype in ArrayDatatypes)
return noModifications
return listOf(IAstModification.ReplaceNode(
declValue,
TypecastExpression(declValue, decl.datatype, true, declValue.position),
decl
))
}
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr2.left, expr2.right)
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr.left, expr.right)
if(toFix!=null) {
when {
toFix===expr2.left -> {
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
expr2.left.linkParents(expr2)
}
toFix===expr2.right -> {
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
expr2.right.linkParents(expr2)
}
return when {
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr))
toFix===expr.right -> listOf(IAstModification.ReplaceNode(
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
else -> throw FatalAstException("confused binary expression side")
}
}
}
return expr2
return noModifications
}
override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment)
if(assg !is Assignment)
return assg
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// see if a typecast is needed to convert the value's type into the proper target type
val valueItype = assg.value.inferType(program)
val targetItype = assg.target.inferType(program, assg)
val valueItype = assignment.value.inferType(program)
val targetItype = assignment.target.inferType(program)
if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
if (valuetype != targettype) {
if (valuetype isAssignableTo targettype) {
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
assg.value.linkParents(assg)
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
return noModifications
return listOf(IAstModification.ReplaceNode(
assignment.value,
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment))
} else {
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> {
val cast = cvalue.cast(targettype)
return if(cast.isValid)
listOf(IAstModification.ReplaceNode(cvalue, cast.valueOrZero(), cvalue.parent))
else
emptyList()
}
val cvalue = assignment.value.constValue(program)
if(cvalue!=null) {
val number = cvalue.number.toDouble()
// more complex comparisons if the type is different, but the constant value is compatible
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
if(number<0x80)
return castLiteral(cvalue)
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
if(number<0x8000)
return castLiteral(cvalue)
}
}
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
return assg
return noModifications
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
return super.visit(functionCallStatement)
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope())
}
override fun visit(functionCall: FunctionCall): Expression {
checkFunctionCallArguments(functionCall, functionCall.definingScope())
return super.visit(functionCall)
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCall, functionCall.definingScope())
}
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter's type
val modifications = mutableListOf<IAstModification>()
when(val sub = call.target.targetStatement(scope)) {
is Subroutine -> {
for(arg in sub.parameters.zip(call.arglist.withIndex())) {
val argItype = arg.second.value.inferType(program)
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
if(argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
val requiredType = arg.first.type
val requiredType = pair.first.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position)
typecasted.linkParents(arg.second.value.parent)
call.arglist[arg.second.index] = typecasted
modifications += IAstModification.ReplaceNode(
call.args[index],
TypecastExpression(pair.second, requiredType, true, pair.second.position),
call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
if(pair.second is IdentifierReference) {
modifications += IAstModification.ReplaceNode(
call.args[index],
AddressOf(pair.second as IdentifierReference, pair.second.position),
call as Node)
}
} else if(pair.second is NumericLiteralValue) {
val cast = (pair.second as NumericLiteralValue).cast(requiredType)
if(cast.isValid)
modifications += IAstModification.ReplaceNode(
call.args[index],
cast.valueOrZero(),
call as Node)
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
}
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name)
if(func.pure) {
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
for (arg in func.parameters.zip(call.arglist.withIndex())) {
val argItype = arg.second.value.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
if (arg.first.possibleDatatypes.any { argtype == it })
continue
for (possibleType in arg.first.possibleDatatypes) {
func.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
if (pair.first.possibleDatatypes.all { argtype != it }) {
for (possibleType in pair.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position)
typecasted.linkParents(arg.second.value.parent)
call.arglist[arg.second.index] = typecasted
modifications += IAstModification.ReplaceNode(
call.args[index],
TypecastExpression(pair.second, possibleType, true, pair.second.position),
call as Node)
break
}
}
@ -116,83 +170,63 @@ internal class TypecastsAdder(private val program: Program): IAstModifyingVisito
}
}
}
null -> {}
else -> throw FatalAstException("call to something weird $sub ${call.target}")
else -> { }
}
return modifications
}
override fun visit(typecast: TypecastExpression): Expression {
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
printWarning("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 super.visit(typecast)
return noModifications
}
override fun visit(memread: DirectMemoryRead): Expression {
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val literaladdr = memread.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
memread.addressExpression.parent = memread
}
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
}
return super.visit(memread)
return noModifications
}
override fun visit(memwrite: DirectMemoryWrite) {
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
memwrite.addressExpression.parent = memwrite
}
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero()
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
}
super.visit(memwrite)
return noModifications
}
override fun visit(structLv: StructLiteralValue): Expression {
val litval = super.visit(structLv)
if(litval !is StructLiteralValue)
return litval
val decl = litval.parent as? VarDecl
if(decl != null) {
val struct = decl.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
} else {
val assign = litval.parent as? Assignment
if (assign != null) {
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
if(decl2 != null) {
val struct = decl2.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
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
val returnValue = returnStmt.value
if(returnValue!=null) {
val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size==1) {
val subReturnType = subroutine.returntypes.first()
if (returnValue.inferType(program).istype(subReturnType))
return noModifications
if (returnValue is NumericLiteralValue) {
val cast = returnValue.cast(subroutine.returntypes.single())
if(cast.isValid)
returnStmt.value = cast.valueOrZero()
} else {
return listOf(IAstModification.ReplaceNode(
returnValue,
TypecastExpression(returnValue, subReturnType, true, returnValue.position),
returnStmt))
}
}
}
return litval
}
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
structLv.values = struct.statements.zip(structLv.values).map {
val memberDt = (it.first as VarDecl).datatype
val valueDt = it.second.inferType(program)
if (valueDt.typeOrElse(memberDt) != memberDt)
TypecastExpression(it.second, memberDt, true, it.second.position)
else
it.second
}
return noModifications
}
}

View File

@ -1,157 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature
internal class VarInitValueAndAddressOfCreator(private val program: Program): IAstModifyingVisitor {
// For VarDecls that declare an initialization value:
// Replace the vardecl with an assignment (to set the initial value),
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
// This makes sure the variables get reset to the intended value on a next run of the program.
// Variable decls without a value don't get this treatment, which means they retain the last
// value they had when restarting the program.
// This is done in a separate step because it interferes with the namespace lookup of symbols
// in other ast processors.
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
override fun visit(module: Module) {
vardeclsToAdd.clear()
super.visit(module)
// add any new vardecls to the various scopes
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
}
override fun visit(decl: VarDecl): Statement {
super.visit(decl)
if(decl.isArray && decl.value==null) {
// array datatype without initialization value, add list of zeros
val arraysize = decl.arraysize!!.size()!!
val array = ArrayLiteralValue(decl.datatype,
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
null, decl.position)
array.addToHeap()
decl.value = array
}
if(decl.type!= VarDeclType.VAR || decl.value==null)
return decl
if(decl.datatype in NumericDatatypes) {
val scope = decl.definingScope()
addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!!
val value =
if(declvalue is NumericLiteralValue)
declvalue.cast(decl.datatype)
else
declvalue
val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".")
return VariableInitializationAssignment(
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
null,
value,
decl.position
)
}
return decl
}
override fun visit(functionCall: FunctionCall): Expression {
var parentStatement: Node = functionCall
while(parentStatement !is Statement)
parentStatement = parentStatement.parent
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, parentStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.arglist, parentStatement)
}
return functionCall
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.arglist, functionCallStatement)
}
return functionCallStatement
}
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<Expression>, parent: Statement) {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) {
if(argparam.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? StringLiteralValue
if(idref!=null) {
val variable = idref.targetVarDecl(program.namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
val pointerExpr = AddressOf(idref, idref.position)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
}
else if(strvalue!=null) {
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl.createAuto(strvalue)
addVarDecl(strvalue.definingScope(), variable)
// replace the argument with &autovar
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
}
}
}
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) {
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for(arg in args.withIndex().zip(signature.parameters)) {
val argvalue = arg.first.value
val argDt = argvalue.inferType(program)
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argvalue !is IdentifierReference)
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
val addrOf = AddressOf(argvalue, argvalue.position)
args[arg.first.index] = addrOf
addrOf.linkParents(parent)
}
}
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
if(declList.all{it.name!=variable.name})
declList.add(variable)
}
}

View File

@ -0,0 +1,44 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.NopStatement
internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
into.statements.remove(scope)
}
}
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
return noModifications
}
}

View File

@ -0,0 +1,87 @@
package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.*
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall) {
val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program)
if(error!=null)
throw CompilerException(error)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program)
if (error!=null)
throw CompilerException(error)
}
companion object {
private fun argTypeCompatible(argDt: DataType, paramDt: DataType): Boolean {
if(argDt==paramDt)
return true
// there are some exceptions that are considered compatible, such as STR <> UWORD
if(argDt==DataType.STR && paramDt==DataType.UWORD ||
argDt==DataType.UWORD && paramDt==DataType.STR)
return true
return false
}
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
val argITypes = call.args.map { it.inferType(program) }
if(argITypes.any { !it.isKnown })
throw FatalAstException("unknown dt")
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope)
if (target is Subroutine) {
if(call.args.size != target.parameters.size)
return "invalid number of arguments"
val paramtypes = target.parameters.map { it.type }
val mismatch = argtypes.zip(paramtypes).indexOfFirst { !argTypeCompatible(it.first, it.second) }
if(mismatch>=0) {
val actual = argtypes[mismatch].toString()
val expected = paramtypes[mismatch].toString()
return "argument ${mismatch + 1} type mismatch, was: $actual expected: $expected"
}
if(target.isAsmSubroutine) {
if(target.asmReturnvaluesRegisters.size>1) {
// multiple return values will NOT work inside an expression.
// they MIGHT work in a regular assignment or just a function call statement.
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
if(call !is FunctionCallStatement && parent !is Assignment && parent !is VarDecl) {
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)"
}
}
}
}
else if (target is BuiltinFunctionStatementPlaceholder) {
val func = BuiltinFunctions.getValue(target.name)
if(call.args.size != func.parameters.size)
return "invalid number of arguments"
val paramtypes = func.parameters.map { it.possibleDatatypes }
argtypes.zip(paramtypes).forEachIndexed { index, pair ->
val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
if (!anyCompatible) {
val actual = pair.first.toString()
val expected = pair.second.toString()
return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
}
}
}
return null
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
package prog8.compiler
internal class AssemblyError(msg: String) : RuntimeException(msg)

View File

@ -0,0 +1,217 @@
package prog8.compiler
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
subroutineVariables.add(decl.name to decl)
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value
if(decl.allowInitializeWithZero)
{
val nextAssign = decl.definingScope().nextSibling(decl) as? Assignment
if (nextAssign != null && nextAssign.target.isSameAs(IdentifierReference(listOf(decl.name), Position.DUMMY)))
decl.value = null
else
decl.value = decl.zeroElementValue()
}
}
return noModifications
}
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, assignment.definingScope()),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
} else {
val assignLeft = Assignment(assignment.target, binExpr.left, assignment.position)
return listOf(
IAstModification.InsertBefore(assignment, assignLeft, assignment.definingScope()),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr))
}
}
}
}
return noModifications
}
private val subroutineVariables = mutableListOf<Pair<String, VarDecl>>()
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
subroutineVariables.clear()
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
subroutineVariables.addAll(decls.map { it.name to it })
val sub = scope.definingSubroutine()
if (sub != null) {
// move vardecls of the scope into the upper scope. Make sure the position remains the same!
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
val replaceVardecls =numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, initValue, it.position)
initValue.parent = assign
IAstModification.ReplaceNode(it, assign, scope)
}
val moveVardeclsUp = decls.map { IAstModification.InsertFirst(it, sub) }
return replaceVardecls + moveVardeclsUp
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val firstDeclarations = mutableMapOf<String, VarDecl>()
for(decl in subroutineVariables) {
val existing = firstDeclarations[decl.first]
if(existing!=null && existing !== decl.second) {
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
} else {
firstDeclarations[decl.first] = decl.second
}
}
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
val outerScope = subroutine.definingScope()
val outerStatements = outerScope.statements
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
if (subroutineStmtIdx > 0
&& outerStatements[subroutineStmtIdx - 1] !is Jump
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
&& outerStatements[subroutineStmtIdx - 1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
}
return mods
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove superfluous typecasts (outside of expressions)
// such as casting byte<->ubyte, word<->uword
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
if(typecast.parent !is Expression) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
// Note: for various reasons (most importantly, code simplicity), the code generator assumes/requires
// that the types of assignment values and their target are the same,
// and that the types of both operands of a binaryexpression node are the same.
// So, it is not easily possible to remove the typecasts that are there to make these conditions true.
// 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.expression is IdentifierReference) {
return listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
parent
))
} else if(typecast.expression is IFunctionCall) {
return listOf(IAstModification.ReplaceNode(
typecast,
typecast.expression,
parent
))
}
} else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
}
}
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,10 @@ data class CompilationOptions(val output: OutputType,
val launcher: LauncherType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean)
val floats: Boolean,
val noSysInit: Boolean) {
var slowCodegenWarnings = false
}
class CompilerException(message: String?) : Exception(message)

View File

@ -4,16 +4,19 @@ import prog8.ast.AstToSourceCode
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.statements.Directive
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.optimizer.*
import prog8.optimizer.UnusedCodeRemover
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule
import prog8.parser.importModule
import prog8.parser.moduleName
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
@ -26,90 +29,44 @@ class CompilationResult(val success: Boolean,
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
compilationTarget: String,
outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
var programName: String? = null
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
var importedFiles: List<Path> = emptyList()
var success=false
when(compilationTarget) {
C64Target.name -> CompilationTarget.instance = C64Target
Cx16Target.name -> CompilationTarget.instance = Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
println("Parsing...")
programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath)
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
importLibraryModule(programAst, "c64lib")
importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importLibraryModule(programAst, "math")
importLibraryModule(programAst, "prog8lib")
// perform initial syntax checks and constant folding
println("Syntax check...")
val time1 = measureTimeMillis {
programAst.checkIdentifiers()
}
//println(" time1: $time1")
val time2 = measureTimeMillis {
programAst.constantFold()
}
//println(" time2: $time2")
val time3 = measureTimeMillis {
programAst.removeNopsFlattenAnonScopes()
programAst.reorderStatements()
programAst.addTypecasts()
}
//println(" time3: $time3")
val time4 = measureTimeMillis {
programAst.checkValid(compilerOptions) // check if tree is valid
}
//println(" time4: $time4")
programAst.checkIdentifiers()
if (optimize) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements()
if (optsDone1 + optsDone2 == 0)
break
}
}
programAst.addTypecasts()
programAst.removeNopsFlattenAnonScopes()
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (optimize)
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly) {
// asm generation directly from the Ast, no need for intermediate code
val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
programAst.anonscopeVarsCleanup()
val assembly = AsmGen(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programName = assembly.name
}
success = true
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, importedFiles)
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
@ -132,16 +89,32 @@ fun compileProgram(filepath: Path,
System.out.flush()
throw x
}
return CompilationResult(success, programAst, programName ?: "", importedFiles)
return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList())
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
println("Compiler target: ${CompilationTarget.instance.name}. Parsing...")
val importer = ModuleImporter()
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
importer.importModule(programAst, filepath)
errors.handle()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// depending on the machine and compiler options we may have to include some libraries
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math")
importer.importLibraryModule(programAst, "prog8_lib")
errors.handle()
return Triple(programAst, compilerOptions, importedFiles)
}
private fun determineCompilationOptions(program: Program): CompilationOptions {
val mainModule = program.modules.first()
@ -149,13 +122,12 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" }
as? Directive)?.args?.single()?.int ?: 0
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val zpType: ZeropageType =
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
@ -165,6 +137,12 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && CompilationTarget.instance.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
@ -172,9 +150,97 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
.map { it[0].int!!..it[1].int!! }
.toList()
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
System.err.println("invalid output type $outputType")
exitProcess(1)
}
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled
zpType, zpReserved, floatsEnabled, noSysInit
)
}
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${CompilationTarget.instance.name}...")
programAst.checkIdentifiers(errors)
errors.handle()
programAst.constantFold(errors)
errors.handle()
programAst.reorderStatements(errors)
errors.handle()
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors)
errors.handle()
programAst.checkIdentifiers(errors)
errors.handle()
}
private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions()
val optsDone3 = programAst.optimizeStatements(errors)
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
errors.handle()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
val remover = UnusedCodeRemover(programAst, errors)
remover.visit(programAst)
remover.applyModifications()
errors.handle()
}
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
errors.handle()
val callGraph = CallGraph(programAst)
callGraph.checkRecursiveCalls(errors)
errors.handle()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
optimize: Boolean, compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
programAst.processAstBeforeAsmGeneration(errors)
errors.handle()
// printAst(programAst)
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
val assembly = CompilationTarget.instance.asmGenerator(
programAst,
errors,
CompilationTarget.instance.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
errors.handle()
return assembly.name
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}

View File

@ -8,6 +8,12 @@ class ZeropageDepletedError(message: String) : Exception(message)
abstract class Zeropage(protected val options: CompilationOptions) {
abstract val SCRATCH_B1 : Int // temp storage for a single byte
abstract val SCRATCH_REG : Int // temp storage for a register
abstract val SCRATCH_W1 : Int // temp storage 1 for a word $fb+$fc
abstract val SCRATCH_W2 : Int // temp storage 2 for a word $fb+$fc
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
@ -15,8 +21,8 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE)
throw CompilerException("zero page usage has been disabled")
@ -28,9 +34,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
DataType.FLOAT -> {
if (options.floats) {
if(position!=null)
printWarning("allocated a large value (float) in zeropage", position)
errors.warn("allocated a large value (float) in zeropage", position)
else
printWarning("$scopedname: allocated a large value (float) in zeropage")
errors.warn("$scopedname: allocated a large value (float) in zeropage", position ?: Position.DUMMY)
5
} else throw CompilerException("floating point option not enabled")
}
@ -39,13 +45,13 @@ abstract class Zeropage(protected val options: CompilationOptions) {
if(free.size > 0) {
if(size==1) {
for(candidate in free.min()!! .. free.max()!!+1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
if(loneByte(candidate))
return makeAllocation(candidate, 1, datatype, scopedname)
}
return makeAllocation(free[0], 1, datatype, scopedname)
}
for(candidate in free.min()!! .. free.max()!!+1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1) {
if (sequentialFree(candidate, size))
return makeAllocation(candidate, size, datatype, scopedname)
}
@ -58,18 +64,10 @@ abstract class Zeropage(protected val options: CompilationOptions) {
private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int {
free.removeAll(address until address+size)
allocations[address] = Pair(name ?: "<unnamed>", datatype)
allocations[address] = (name ?: "<unnamed>") to datatype
return address
}
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
enum class ExitProgramStrategy {
CLEAN_EXIT,
SYSTEM_RESET
}
abstract val exitProgramStrategy: ExitProgramStrategy
}

View File

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

View File

@ -0,0 +1,16 @@
package prog8.compiler.target
import prog8.compiler.CompilationOptions
internal interface IAssemblyGenerator {
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
}
internal const val generatedLabelPrefix = "_prog8_label_"
internal const val subroutineFloatEvalResultVar1 = "_prog8_float_eval_result1"
internal const val subroutineFloatEvalResultVar2 = "_prog8_float_eval_result2"
internal interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions)
}

View File

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

View File

@ -2,44 +2,33 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path
import kotlin.system.exitProcess
class AssemblyProgram(val name: String, outputDir: Path) {
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg")
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
companion object {
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
}
fun assemble(options: CompilationOptions) {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
override fun assemble(options: CompilationOptions) {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
val outFile = when(options.output) {
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating C-64 prg.")
println("\nCreating prg for target ${CompilationTarget.instance.name}.")
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary.")
println("\nCreating raw binary for target ${CompilationTarget.instance.name}.")
binFile
}
}
@ -47,27 +36,39 @@ class AssemblyProgram(val name: String, outputDir: Path) {
val proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor()
if(result!=0) {
if (result != 0) {
System.err.println("assembler failed with returncode $result")
exitProcess(result)
}
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
private fun removeGeneratedLabelsFromMonlist() {
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
val lines = viceMonListFile.toFile().readLines()
viceMonListFile.toFile().outputStream().bufferedWriter().use {
for (line in lines) {
if(pattern.matchEntire(line)==null)
it.write(line+"\n")
}
}
}
private fun generateBreakpointList() {
// builds list of breakpoints, appends to monitor list file
val breakpoints = mutableListOf<String>()
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them
for(line in viceMonListFile.toFile().readLines()) {
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
for (line in viceMonListFile.toFile().readLines()) {
val match = pattern.matchEntire(line)
if(match!=null)
breakpoints.add("break \$" + match.groupValues[1])
if (match != null)
breakpoints.add("break \$" + match.groupValues[1])
}
val num = breakpoints.size
breakpoints.add(0, "; vice monitor breakpoint list now follows")
breakpoints.add(1, "; $num breakpoints have been defined")
breakpoints.add(2, "del")
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n")+"\n")
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
}
}

View File

@ -0,0 +1,243 @@
package prog8.compiler.target.c64
import prog8.ast.Program
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat
import prog8.parser.ModuleImporter
import java.io.IOException
import java.math.RoundingMode
import kotlin.math.absoluteValue
import kotlin.math.pow
internal object C64MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
// 5-byte cbm MFLPT format limitations:
override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MEM_SIZE = 5
override val POINTER_MEM_SIZE = 2
override val BASIC_LOAD_ADDRESS = 0x0801
override val RAW_LOAD_ADDRESS = 0xc000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
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
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x02 // temp storage for a single byte
override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
init {
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9)
free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x16, 0x17, 0x18, 0x19, 0x1a,
0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area'
))
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list
free.removeAll(listOf(
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
))
}
if(options.zeropage!=ZeropageType.DONTUSE) {
// add the free Zp addresses
// these are valid for the C-64 but allow BASIC to keep running fully *as long as you don't use tape I/O*
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
0x92, 0x96, 0x9b, 0x9c, 0x9e, 0x9f, 0xa5, 0xa6,
0xb0, 0xb1, 0xbe, 0xbf, 0xf9))
} else {
// don't use the zeropage at all
free.clear()
}
}
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)
}
}
internal data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short): IMachineFloat {
companion object {
val zero = Mflpt5(0, 0, 0, 0, 0)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
// and https://en.wikipedia.org/wiki/IEEE_754-1985
val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt == 0.0)
return zero
val sign = if (flt < 0.0) 0x80L else 0x00L
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
var mantissa = flt.absoluteValue
// if mantissa is too large, shift right and adjust exponent
while (mantissa >= 0x100000000) {
mantissa /= 2.0
exponent++
}
// if mantissa is too small, shift left and adjust exponent
while (mantissa < 0x80000000) {
mantissa *= 2.0
exponent--
}
return when {
exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw CompilerException("floating point overflow: $this")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toShort(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
(mantLong.and(0x000000ffL)).toShort())
}
}
}
}
override fun toDouble(): Double {
if (this == zero) return 0.0
val exp = b0 - 128
val sign = (b1.toInt() and 0x80) > 0
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
return if (sign) -result else result
}
override fun makeFloatFillAsm(): String {
val b0 = "$" + b0.toString(16).padStart(2, '0')
val b1 = "$" + b1.toString(16).padStart(2, '0')
val b2 = "$" + b2.toString(16).padStart(2, '0')
val b3 = "$" + b3.toString(16).padStart(2, '0')
val b4 = "$" + b4.toString(16).padStart(2, '0')
return "$b0, $b1, $b2, $b3, $b4"
}
}
}

View File

@ -1,255 +0,0 @@
package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.CompilerException
import prog8.compiler.Zeropage
import prog8.compiler.ZeropageType
import java.awt.Color
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import kotlin.math.absoluteValue
import kotlin.math.pow
object MachineDefinition {
// 5-byte cbm MFLPT format limitations:
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
const val BASIC_LOAD_ADDRESS = 0x0801
const val RAW_LOAD_ADDRESS = 0xc000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above
const val ESTACK_LO_VALUE = 0xce00 // $ce00-$ceff inclusive
const val ESTACK_HI_VALUE = 0xcf00 // $cf00-$cfff inclusive
const val ESTACK_LO_HEX = "\$ce00"
const val ESTACK_LO_PLUS1_HEX = "\$ce01"
const val ESTACK_LO_PLUS2_HEX = "\$ce02"
const val ESTACK_HI_HEX = "\$cf00"
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
const val ESTACK_HI_PLUS2_HEX = "\$cf02"
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
companion object {
const val SCRATCH_B1 = 0x02
const val SCRATCH_REG = 0x03 // temp storage for a register
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
const val SCRATCH_W1 = 0xfb // $fb+$fc
const val SCRATCH_W2 = 0xfd // $fd+$fe
}
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
ZeropageType.BASICSAFE, ZeropageType.DONTUSE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if (options.floats && options.zeropage !in setOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9)
free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area'
))
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list
free.removeAll(listOf(
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
))
}
if(options.zeropage!=ZeropageType.DONTUSE) {
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
free.addAll(listOf(0x04, 0x05, 0x06, 0x0a, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
} else {
// don't use the zeropage at all
free.clear()
}
}
assert(SCRATCH_B1 !in free)
assert(SCRATCH_REG !in free)
assert(SCRATCH_REG_X !in free)
assert(SCRATCH_W1 !in free)
assert(SCRATCH_W2 !in free)
for (reserved in options.zpReserved)
reserve(reserved)
}
}
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
companion object {
const val MemorySize = 5
val zero = Mflpt5(0, 0, 0, 0, 0)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
// and https://en.wikipedia.org/wiki/IEEE_754-1985
val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt == 0.0)
return zero
val sign = if (flt < 0.0) 0x80L else 0x00L
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
var mantissa = flt.absoluteValue
// if mantissa is too large, shift right and adjust exponent
while (mantissa >= 0x100000000) {
mantissa /= 2.0
exponent++
}
// if mantissa is too small, shift left and adjust exponent
while (mantissa < 0x80000000) {
mantissa *= 2.0
exponent--
}
return when {
exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw CompilerException("floating point overflow: $this")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toShort(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
(mantLong.and(0x000000ffL)).toShort())
}
}
}
}
fun toDouble(): Double {
if (this == zero) return 0.0
val exp = b0 - 128
val sign = (b1.toInt() and 0x80) > 0
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
return if (sign) -result else result
}
}
object Charset {
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
transparent.createGraphics().drawImage(img, 0, 0, null)
val black = Color(0, 0, 0).rgb
val nopixel = Color(0, 0, 0, 0).rgb
for (y in 0 until transparent.height) {
for (x in 0 until transparent.width) {
val col = transparent.getRGB(x, y)
if (col == black)
transparent.setRGB(x, y, nopixel)
}
}
val numColumns = transparent.width / 8
val charImages = (0..255).map {
val charX = it % numColumns
val charY = it / numColumns
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
}
return charImages.toTypedArray()
}
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
)
}

View File

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

View File

@ -1,48 +0,0 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.NameError
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Statement
import prog8.ast.statements.VarDecl
class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
fun result(): List<AstException> {
return checkResult
}
override fun visit(program: Program) {
varsToMove.clear()
super.visit(program)
for((scope, decls) in varsToMove) {
val sub = scope.definingSubroutine()!!
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing!=null) {
checkResult.add(NameError("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position))
conflicts = true
}
}
if (!conflicts) {
decls.forEach { scope.remove(it) }
sub.statements.addAll(0, decls)
decls.forEach { it.parent = sub }
}
}
}
override fun visit(scope: AnonymousScope): Statement {
val scope2 = super.visit(scope) as AnonymousScope
val vardecls = scope2.statements.filterIsInstance<VarDecl>()
varsToMove[scope2] = vardecls
return scope2
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,5 @@
package prog8.compiler.target.c64.codegen
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
@ -13,43 +10,45 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
var linesByFour = getLinesBy(lines, 4)
var removeLines = optimizeUselessStackByteWrites(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
var mods = optimizeUselessStackByteWrites(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeIncDec(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeIncDec(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeCmpSequence(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeCmpSequence(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeStoreLoadSame(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeStoreLoadSame(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
mods= optimizeJsrRts(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
var linesByFourteen = getLinesBy(lines, 14)
removeLines = optimizeSameAssignments(linesByFourteen)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeSameAssignments(linesByFourteen)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14)
numberOfOptimizations++
}
@ -59,7 +58,22 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
return numberOfOptimizations
}
fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
if(modification.remove)
lines.removeAt(modification.lineIndex)
else
lines[modification.lineIndex] = modification.replacement!!
}
}
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
// all lines (that aren't empty or comments) in sliding windows of certain size
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// the when statement (on bytes) generates a sequence of:
// lda $ce01,x
// cmp #$20
@ -68,42 +82,42 @@ fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int
// cmp #$21
// beq check_prog8_s73choice_33
// the repeated lda can be removed
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
if(lines[0].value.trim()=="lda P8ESTACK_LO+1,x" &&
lines[1].value.trim().startsWith("cmp ") &&
lines[2].value.trim().startsWith("beq ") &&
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
removeLines.add(lines[3].index) // remove the second lda
lines[3].value.trim()=="lda P8ESTACK_LO+1,x") {
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
}
}
return removeLines
return mods
}
fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
// this is a lot harder for word values because the instruction sequence varies.
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
if(lines[0].value.trim()=="sta P8ESTACK_LO,x" &&
lines[1].value.trim()=="dex" &&
lines[2].value.trim()=="inx" &&
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
removeLines.add(lines[1].index)
removeLines.add(lines[2].index)
removeLines.add(lines[3].index)
lines[3].value.trim()=="lda P8ESTACK_LO,x") {
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
}
return removeLines
return mods
}
fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check...
// @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for (pair in linesByFourteen) {
val first = pair[0].value.trimStart()
val second = pair[1].value.trimStart()
@ -122,8 +136,8 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
val fourthvalue = sixth.substring(4)
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
removeLines.add(pair[4].index)
removeLines.add(pair[5].index)
mods.add(Modification(pair[4].index, true, null))
mods.add(Modification(pair[5].index, true, null))
}
}
@ -132,12 +146,13 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
val secondvalue = third.substring(4)
if(firstvalue==secondvalue) {
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
removeLines.add(pair[2].index)
mods.add(Modification(pair[2].index, true, null))
}
}
if(first.startsWith("lda") && second.startsWith("ldy") && third.startsWith("sta") && fourth.startsWith("sty") &&
fifth.startsWith("lda") && sixth.startsWith("ldy") && seventh.startsWith("jsr c64flt.copy_float")) {
fifth.startsWith("lda") && sixth.startsWith("ldy") &&
(seventh.startsWith("jsr floats.copy_float") || seventh.startsWith("jsr cx16flt.copy_float"))) {
val nineth = pair[8].value.trimStart()
val tenth = pair[9].value.trimStart()
@ -147,28 +162,26 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
val fourteenth = pair[13].value.trimStart()
if(eighth.startsWith("lda") && nineth.startsWith("ldy") && tenth.startsWith("sta") && eleventh.startsWith("sty") &&
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") && fourteenth.startsWith("jsr c64flt.copy_float")) {
twelveth.startsWith("lda") && thirteenth.startsWith("ldy") &&
(fourteenth.startsWith("jsr floats.copy_float") || fourteenth.startsWith("jsr cx16flt.copy_float"))) {
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
// identical float init
removeLines.add(pair[7].index)
removeLines.add(pair[8].index)
removeLines.add(pair[9].index)
removeLines.add(pair[10].index)
mods.add(Modification(pair[7].index, true, null))
mods.add(Modification(pair[8].index, true, null))
mods.add(Modification(pair[9].index, true, null))
mods.add(Modification(pair[10].index, true, null))
}
}
}
}
return removeLines
return mods
}
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
// all lines (that aren't empty or comments) in sliding windows of certain size
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
val removeLines = mutableListOf<Int>()
// TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value.trimStart()
val second = pair[1].value.trimStart()
@ -183,29 +196,43 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx "))
) {
val firstLoc = first.substring(4)
val secondLoc = second.substring(4)
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
removeLines.add(pair[1].index)
mods.add(Modification(pair[1].index, true, null))
}
}
}
return removeLines
return mods
}
private fun optimizeIncDec(linesByTwo: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
val removeLines = mutableListOf<Int>()
for (pair in linesByTwo) {
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value
val second = pair[1].value
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
removeLines.add(pair[0].index)
removeLines.add(pair[1].index)
mods.add(Modification(pair[0].index, true, null))
mods.add(Modification(pair[1].index, true, null))
}
}
return removeLines
return mods
}
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// jsr Sub + rts -> jmp Sub
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value
val second = pair[1].value
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(pair[0].index, false, pair[0].value.replace("jsr", "jmp"))
mods += Modification(pair[1].index, true, null)
}
}
return mods
}

View File

@ -1,745 +0,0 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.VarDecl
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.toHex
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(assign: Assignment) {
if(assign.aug_op!=null)
throw AssemblyError("aug-op assignments should have been transformed to normal ones")
when(assign.value) {
is NumericLiteralValue -> {
val numVal = assign.value as NumericLiteralValue
when(numVal.type) {
DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort())
DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt())
DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble())
else -> throw AssemblyError("weird numval type")
}
}
is RegisterExpr -> {
assignFromRegister(assign.target, (assign.value as RegisterExpr).register)
}
is IdentifierReference -> {
val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT)
when(type) {
DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, assign.value as IdentifierReference)
DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, assign.value as IdentifierReference)
DataType.FLOAT -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
else -> throw AssemblyError("unsupported assignment target type $type")
}
}
is AddressOf -> {
val identifier = (assign.value as AddressOf).identifier
assignFromAddressOf(assign.target, identifier)
}
is DirectMemoryRead -> {
val read = (assign.value as DirectMemoryRead)
when(read.addressExpression) {
is NumericLiteralValue -> {
val address = (read.addressExpression as NumericLiteralValue).number.toInt()
assignFromMemoryByte(assign.target, address, null)
}
is IdentifierReference -> {
assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(read.addressExpression)
TODO("read memory byte from result and put that in ${assign.target}")
}
}
}
is PrefixExpression -> {
// TODO optimize common cases
asmgen.translateExpression(assign.value as PrefixExpression)
assignFromEvalResult(assign.target)
}
is BinaryExpression -> {
// TODO optimize common cases
asmgen.translateExpression(assign.value as BinaryExpression)
assignFromEvalResult(assign.target)
}
is ArrayIndexedExpression -> {
// TODO optimize common cases
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
val index = arrayExpr.arrayspec.index
if(index is NumericLiteralValue) {
// constant array index value
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
val indexValue = index.number.toInt() * ArrayElementTypes.getValue(arrayDt).memorySize()
when (arrayDt) {
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
DataType.ARRAY_UW, DataType.ARRAY_W ->
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $arrayVarName+$indexValue+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
DataType.ARRAY_F ->
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
else ->
throw AssemblyError("weird array type")
}
} else {
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
}
assignFromEvalResult(assign.target)
}
is TypecastExpression -> {
val cast = assign.value as TypecastExpression
val sourceType = cast.expression.inferType(program)
val targetType = assign.target.inferType(program, assign)
if(sourceType.isKnown && targetType.isKnown &&
(sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && targetType.typeOrElse(DataType.STRUCT) in ByteDatatypes) ||
(sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && targetType.typeOrElse(DataType.STRUCT) in WordDatatypes)) {
// no need for a type cast
assign.value = cast.expression
translate(assign)
} else {
asmgen.translateExpression(assign.value as TypecastExpression)
assignFromEvalResult(assign.target)
}
}
is FunctionCall -> {
asmgen.translateExpression(assign.value as FunctionCall)
assignFromEvalResult(assign.target)
}
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
}
}
internal fun assignFromEvalResult(target: AssignTarget) {
val targetIdent = target.identifier
when {
target.register!=null -> {
if(target.register== Register.X)
throw AssemblyError("can't pop into X register - use variable instead")
asmgen.out(" inx | ld${target.register.name.toLowerCase()} ${MachineDefinition.ESTACK_LO_HEX},x ")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
val targetDt = targetIdent.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $targetName")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sta $targetName
lda ${MachineDefinition.ESTACK_HI_HEX},x
sta $targetName+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<$targetName
ldy #>$targetName
jsr c64flt.pop_float
""")
}
else -> throw AssemblyError("weird target variable type $targetDt")
}
}
target.memoryAddress!=null -> {
asmgen.out(" inx | ldy ${MachineDefinition.ESTACK_LO_HEX},x")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
target.arrayindexed!=null -> {
val arrayDt = target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!.datatype
val arrayVarName = asmgen.asmIdentifierName(target.arrayindexed!!.identifier)
asmgen.translateExpression(target.arrayindexed!!.arrayspec.index)
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
popAndWriteArrayvalueWithIndexA(arrayDt, arrayVarName)
}
else -> throw AssemblyError("weird assignment target $target")
}
}
internal fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
val struct = name.memberOfStruct(program.namespace)
val sourceName = if(struct!=null) {
// take the address of the first struct member instead
val decl = name.targetVarDecl(program.namespace)!!
val firstStructMember = struct.nameOfFirstMember()
// find the flattened var that belongs to this first struct member
val firstVarName = listOf(decl.name, firstStructMember)
val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl
firstVar.name
} else {
asmgen.fixNameSymbols(name.nameInSource.joinToString ("."))
}
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
sta $targetName
sty $targetName+1
""")
}
target.memoryAddress!=null -> {
TODO("assign address $sourceName to memory word $target")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign address $sourceName to array $targetName [ $index ]")
}
else -> TODO("assign address $sourceName to $target")
}
}
internal fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $sourceName
ldy $sourceName+1
sta $targetName
sty $targetName+1
""")
}
target.memoryAddress!=null -> {
TODO("assign wordvar $sourceName to memory ${target.memoryAddress}")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $sourceName+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
}
else -> TODO("assign wordvar to $target")
}
}
internal fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $sourceName
sta $targetName
lda $sourceName+1
sta $targetName+1
lda $sourceName+2
sta $targetName+2
lda $sourceName+3
sta $targetName+3
lda $sourceName+4
sta $targetName+4
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
asmgen.translateExpression(index)
asmgen.out(" lda #<$targetName | ldy #>$targetName | jsr c64flt.pop_float_to_indexed_var")
}
else -> TODO("assign floatvar to $target")
}
}
internal fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
target.register!=null -> {
asmgen.out(" ld${target.register.name.toLowerCase()} $sourceName")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $sourceName
sta $targetName
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
}
target.memoryAddress != null -> {
val addressExpr = target.memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
when {
addressLv != null -> asmgen.out(" lda $sourceName | sta ${addressLv.number.toHex()}")
addressExpr is IdentifierReference -> {
val targetName = asmgen.asmIdentifierName(addressExpr)
asmgen.out(" lda $sourceName | sta $targetName")
}
else -> {
asmgen.translateExpression(addressExpr)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
ldy ${MachineDefinition.ESTACK_HI_HEX},x
sta (+) +1
sty (+) +2
lda $sourceName
+ sta ${65535.toHex()} ; modified
""")
}
}
}
else -> TODO("assign bytevar to $target")
}
}
internal fun assignFromRegister(target: AssignTarget, register: Register) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out(" st${register.name.toLowerCase()} $targetName")
}
target.register!=null -> {
when(register) {
Register.A -> when(target.register) {
Register.A -> {}
Register.X -> asmgen.out(" tax")
Register.Y -> asmgen.out(" tay")
}
Register.X -> when(target.register) {
Register.A -> asmgen.out(" txa")
Register.X -> {}
Register.Y -> asmgen.out(" txy")
}
Register.Y -> when(target.register) {
Register.A -> asmgen.out(" tya")
Register.X -> asmgen.out(" tyx")
Register.Y -> {}
}
}
}
target.memoryAddress!=null -> {
storeRegisterInMemoryAddress(register, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
when (index) {
is NumericLiteralValue -> {
val memindex = index.number.toInt()
when(register) {
Register.A -> asmgen.out(" sta $targetName+$memindex")
Register.X -> asmgen.out(" stx $targetName+$memindex")
Register.Y -> asmgen.out(" sty $targetName+$memindex")
}
}
is RegisterExpr -> {
when(register) {
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
}
when(index.register) {
Register.A -> {}
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
}
asmgen.out("""
tay
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
sta $targetName,y
""")
}
is IdentifierReference -> {
when(register) {
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
}
asmgen.out("""
lda ${asmgen.asmIdentifierName(index)}
tay
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
sta $targetName,y
""")
}
else -> {
asmgen.saveRegister(register)
asmgen.translateExpression(index)
asmgen.restoreRegister(register)
when(register) {
Register.A -> asmgen.out(" sta ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.X -> asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
Register.Y -> asmgen.out(" sty ${MachineDefinition.C64Zeropage.SCRATCH_B1}")
}
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
tay
lda ${MachineDefinition.C64Zeropage.SCRATCH_B1}
sta $targetName,y
""")
}
}
}
else -> TODO("assign register $register to $target")
}
}
private fun storeRegisterInMemoryAddress(register: Register, memoryAddress: DirectMemoryWrite) {
val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
val registerName = register.name.toLowerCase()
when {
addressLv != null -> asmgen.out(" st$registerName ${addressLv.number.toHex()}")
addressExpr is IdentifierReference -> {
val targetName = asmgen.asmIdentifierName(addressExpr)
when(register) {
Register.A -> asmgen.out("""
ldy $targetName
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
""")
Register.X -> asmgen.out("""
txa
ldy $targetName
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
""")
Register.Y -> asmgen.out("""
tya
ldy $targetName
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${MachineDefinition.C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${MachineDefinition.C64Zeropage.SCRATCH_W1}),y
""")
}
}
else -> {
asmgen.saveRegister(register)
asmgen.translateExpression(addressExpr)
asmgen.restoreRegister(register)
when (register) {
Register.A -> asmgen.out(" tay")
Register.X -> throw AssemblyError("can't use X register here")
Register.Y -> {}
}
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
sta (+) +1
lda ${MachineDefinition.ESTACK_HI_HEX},x
sta (+) +2
+ sty ${65535.toHex()} ; modified
""")
}
}
}
internal fun assignFromWordConstant(target: AssignTarget, word: Int) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
if(word ushr 8 == word and 255) {
// lsb=msb
asmgen.out("""
lda #${(word and 255).toHex()}
sta $targetName
sta $targetName+1
""")
} else {
asmgen.out("""
lda #<${word.toHex()}
ldy #>${word.toHex()}
sta $targetName
sty $targetName+1
""")
}
}
target.memoryAddress!=null -> {
TODO("assign word $word to memory ${target.memoryAddress}")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
// TODO optimize common cases
asmgen.translateExpression(index)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
asl a
tay
lda #<${word.toHex()}
sta $targetName,y
lda #>${word.toHex()}
sta $targetName+1,y
""")
}
else -> TODO("assign word $word to $target")
}
}
internal fun assignFromByteConstant(target: AssignTarget, byte: Short) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
target.register!=null -> {
asmgen.out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out(" lda #${byte.toHex()} | sta $targetName ")
}
target.memoryAddress!=null -> {
asmgen.out(" ldy #${byte.toHex()}")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
// TODO optimize common cases
asmgen.translateExpression(index)
asmgen.out("""
inx
ldy ${MachineDefinition.ESTACK_LO_HEX},x
lda #${byte.toHex()}
sta $targetName,y
""")
}
else -> TODO("assign byte $byte to $target")
}
}
internal fun assignFromFloatConstant(target: AssignTarget, float: Double) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
if(float==0.0) {
// optimized case for float zero
when {
targetIdent != null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda #0
sta $targetName
sta $targetName+1
sta $targetName+2
sta $targetName+3
sta $targetName+4
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize
asmgen.out("""
lda #0
sta $targetName+$indexValue
sta $targetName+$indexValue+1
sta $targetName+$indexValue+2
sta $targetName+$indexValue+3
sta $targetName+$indexValue+4
""")
} else {
asmgen.translateExpression(index)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
asl a
asl a
clc
adc ${MachineDefinition.ESTACK_LO_HEX},x
tay
lda #0
sta $targetName,y
sta $targetName+1,y
sta $targetName+2,y
sta $targetName+3,y
sta $targetName+4,y
""") // TODO use a subroutine for this
}
}
else -> TODO("assign float 0.0 to $target")
}
} else {
// non-zero value
val constFloat = asmgen.getFloatConst(float)
when {
targetIdent != null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $constFloat
sta $targetName
lda $constFloat+1
sta $targetName+1
lda $constFloat+2
sta $targetName+2
lda $constFloat+3
sta $targetName+3
lda $constFloat+4
sta $targetName+4
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val arrayVarName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * MachineDefinition.Mflpt5.MemorySize
asmgen.out("""
lda $constFloat
sta $arrayVarName+$indexValue
lda $constFloat+1
sta $arrayVarName+$indexValue+1
lda $constFloat+2
sta $arrayVarName+$indexValue+2
lda $constFloat+3
sta $arrayVarName+$indexValue+3
lda $constFloat+4
sta $arrayVarName+$indexValue+4
""")
} else {
asmgen.translateArrayIndexIntoA(targetArrayIdx)
asmgen.out("""
sta ${MachineDefinition.C64Zeropage.SCRATCH_REG}
asl a
asl a
clc
adc ${MachineDefinition.C64Zeropage.SCRATCH_REG}
tay
lda $constFloat
sta $arrayVarName,y
lda $constFloat+1
sta $arrayVarName+1,y
lda $constFloat+2
sta $arrayVarName+2,y
lda $constFloat+3
sta $arrayVarName+3,y
lda $constFloat+4
sta $arrayVarName+4,y
""") // TODO use a subroutine for this
}
}
else -> TODO("assign float $float to $target")
}
}
}
internal fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
if(address!=null) {
when {
target.register!=null -> {
asmgen.out(" ld${target.register.name.toLowerCase()} ${address.toHex()}")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda ${address.toHex()}
sta $targetName
""")
}
target.memoryAddress!=null -> {
asmgen.out(" ldy ${address.toHex()}")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign memory byte at $address to array $targetName [ $index ]")
}
else -> TODO("assign memory byte $target")
}
}
else if(identifier!=null) {
val sourceName = asmgen.asmIdentifierName(identifier)
when {
target.register!=null -> {
asmgen.out("""
ldy #0
lda ($sourceName),y
""")
when(target.register){
Register.A -> {}
Register.X -> asmgen.out(" tax")
Register.Y -> asmgen.out(" tay")
}
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
ldy #0
lda ($sourceName),y
sta $targetName
""")
}
target.memoryAddress!=null -> {
asmgen.out(" ldy $sourceName")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign memory byte $sourceName to array $targetName [ $index ]")
}
else -> TODO("assign memory byte $target")
}
}
}
private fun popAndWriteArrayvalueWithIndexA(arrayDt: DataType, variablename: String) {
when (arrayDt) {
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
asmgen.out(" tay | inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $variablename,y")
DataType.ARRAY_UW, DataType.ARRAY_W ->
asmgen.out(" asl a | tay | inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | sta $variablename,y | lda ${MachineDefinition.ESTACK_HI_HEX},x | sta $variablename+1,y")
DataType.ARRAY_F ->
// index * 5 is done in the subroutine that's called
asmgen.out("""
sta ${MachineDefinition.ESTACK_LO_HEX},x
dex
lda #<$variablename
ldy #>$variablename
jsr c64flt.pop_float_to_indexed_var
""")
else ->
throw AssemblyError("weird array type")
}
}
}

View File

@ -1,233 +1,268 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.assignment.*
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCall(stmt: IFunctionCall) {
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program.namespace)!!
val preserveStatusRegisterAfterCall = sub.asmReturnvaluesRegisters.any {it.statusflag!=null}
translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
// functioncalls no longer return results on the stack, so simply ignore the results in the registers
if(preserveStatusRegisterAfterCall)
asmgen.out(" plp\t; restore status flags from call")
}
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
// output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values!
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(Register.X in sub.asmClobbers)
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
if(saveX)
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine()!!)
val subName = asmgen.asmIdentifierName(stmt.target)
if(stmt.arglist.isNotEmpty()) {
for(arg in sub.parameters.withIndex().zip(stmt.arglist)) {
translateFuncArguments(arg.first, arg.second, sub)
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
} else {
// multiple register arguments, risk of register clobbering.
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
when {
stmt.args.all {it is AddressOf ||
it is NumericLiteralValue ||
it is StringLiteralValue ||
it is ArrayLiteralValue ||
it is IdentifierReference} -> {
// no risk of clobbering for these simple argument types. Optimize the register loading.
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaRegister(sub, arg.first, arg.second)
}
}
else -> {
// Risk of clobbering due to complex expression args. Work via the stack.
registerArgsViaStackEvaluation(stmt, sub)
}
}
}
}
}
asmgen.out(" jsr $subName")
if(Register.X in sub.asmClobbers)
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
if(preserveStatusRegisterAfterCall) {
asmgen.out(" php\t; save status flags from call")
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
// must take care of popping this value again at the end!
}
if(saveX)
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
}
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
val sourceIDt = value.inferType(program)
if(!sourceIDt.isKnown)
throw AssemblyError("arg type unknown")
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
if(sub.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
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.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("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) {
// pass parameter via a variable
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
target.linkParents(value.parent)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
when(parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
else -> throw AssemblyError("weird parameter datatype")
}
}
is IdentifierReference -> {
// optimize when the argument is a variable
when (parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
else -> throw AssemblyError("weird parameter datatype")
}
}
is RegisterExpr -> {
asmgen.assignFromRegister(target, value.register)
}
is DirectMemoryRead -> {
when(value.addressExpression) {
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val requiredDt = parameter.value.type
if(requiredDt!=valueDt) {
if(valueDt largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types")
}
when {
statusflag!=null -> {
if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
asmgen.assignFromMemoryByte(target, address, null)
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, Register.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
}
}
} else {
// pass parameter via a register parameter
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
}
statusflag!=null -> {
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
asmgen.out("""
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
lda $sourceName
beq +
sec
bcs ++
+ clc
+
+ pla
""")
}
is RegisterExpr -> {
when(value.register) {
Register.A -> asmgen.out(" cmp #0")
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
}
asmgen.out("""
beq +
sec
bcs ++
+ clc
+
""")
}
else -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
beq +
sec
bcs ++
+ clc
+
""")
}
}
}
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 ${MachineDefinition.ESTACK_LO_HEX},x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy ${MachineDefinition.ESTACK_LO_HEX},x")
else -> throw AssemblyError("cannot assign to register pair")
}
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
beq +
sec
bcs ++
+ clc
+""")
}
}
}
register!=null && register.name.length==2 -> {
// register pair as a 16-bit value (only possible for subroutine parameters)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
val hex = value.number.toHex()
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
else -> {}
}
}
is AddressOf -> {
// optimize when the argument is an address of something
val sourceName = asmgen.asmIdentifierName(value.identifier)
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
else -> {}
}
}
else -> {
asmgen.translateExpression(value)
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
throw AssemblyError("can't use X register here - use a variable")
else if (register == RegisterOrPair.AY)
asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x | ldy ${MachineDefinition.ESTACK_HI_HEX},x")
else throw AssemblyError("can only use Carry as status flag parameter")
}
else -> {
// via register or register pair
register!!
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
asmgen.signExtendVariableLsb(scratchVar, valueDt)
asmgen.assignVariableToRegister(scratchVar, register)
}
else {
val target = AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
}
}
}
}
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType in ByteDatatypes && paramType in ByteDatatypes)
return true
if(argType in WordDatatypes && paramType in WordDatatypes)
return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
if(argType in StringDatatypes && paramType==DataType.UWORD)
if(argType==DataType.STR && paramType==DataType.UWORD)
return true
if(argType==DataType.UWORD && paramType in StringDatatypes)
if(argType==DataType.UWORD && paramType == DataType.STR)
return true
return false

View File

@ -4,39 +4,22 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RegisterExpr
import prog8.ast.statements.PostIncrDecr
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.AssemblyError
import prog8.compiler.toHex
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(stmt: PostIncrDecr) {
val incr = stmt.operator=="++"
val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed
val targetRegister = stmt.target.register
val scope = stmt.definingSubroutine()
when {
targetRegister!=null -> {
when(targetRegister) {
Register.A -> {
if(incr)
asmgen.out(" clc | adc #1 ")
else
asmgen.out(" sec | sbc #1 ")
}
Register.X -> {
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
}
Register.Y -> {
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
}
}
}
targetIdent!=null -> {
val what = asmgen.asmIdentifierName(targetIdent)
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)
when (dt) {
val what = asmgen.asmVariableName(targetIdent)
when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> {
if(incr)
@ -51,7 +34,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
DataType.FLOAT -> {
asmgen.out(" lda #<$what | ldy #>$what")
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
@ -63,86 +46,82 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
asmgen.out(if(incr) " inc $what" else " dec $what")
}
is IdentifierReference -> {
val what = asmgen.asmIdentifierName(addressExpr)
asmgen.out(if(incr) " inc $what" else " dec $what")
val what = asmgen.asmVariableName(addressExpr)
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
else -> {
asmgen.assignExpressionToRegister(addressExpr, RegisterOrPair.AY)
asmgen.out(" sta (+) + 1 | sty (+) + 2")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
else -> throw AssemblyError("weird target type $targetMemory")
}
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val what = asmgen.asmIdentifierName(targetArrayIdx.identifier)
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
val elementDt = ArrayElementTypes.getValue(arrayDt)
when(index) {
is NumericLiteralValue -> {
val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+")
else
asmgen.out("""
lda $what+$indexValue
bne +
dec $what+$indexValue+1
+ dec $what+$indexValue
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
if(targetArrayIdx.indexer.indexNum!=null) {
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname+$indexValue | bne + | inc $asmArrayvarname+$indexValue+1 |+")
else
asmgen.out("""
lda $asmArrayvarname+$indexValue
bne +
dec $asmArrayvarname+$indexValue+1
+ dec $asmArrayvarname+$indexValue
""")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$what+$indexValue | ldy #>$what+$indexValue")
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
is RegisterExpr -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
is IdentifierReference -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
else -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
DataType.FLOAT -> {
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
}
else -> throw AssemblyError("weird target type ${stmt.target}")
}
}
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X} | tax")
when(arrayDt) {
DataType.STR, DataType.STR_S,
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
{
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X, false, scope!!)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(if(incr) " inc $asmArrayvarname,x" else " dec $asmArrayvarname,x")
}
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $asmArrayvarname,x | bne + | inc $asmArrayvarname+1,x |+")
else
asmgen.out("""
lda $asmArrayvarname,x
bne +
dec $asmArrayvarname+1,x
+ dec $asmArrayvarname
""")
}
DataType.FLOAT -> {
asmgen.out("""
ldy #>$asmArrayvarname
clc
adc #<$asmArrayvarname
bcc +
iny
+ jsr floats.inc_var_f""")
}
else -> throw AssemblyError("weird array elt dt")
}
asmgen.restoreRegister(CpuRegister.X, false)
}
}
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 ${MachineDefinition.C64Zeropage.SCRATCH_REG_X}")
}
}

View File

@ -0,0 +1,194 @@
package prog8.compiler.target.c64.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.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,
private val program: Program,
private val asmgen: AsmGen,
val datatype: DataType,
val scope: Subroutine?,
private val variableAsmName: String? = 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?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.arrayvar)
lateinit var origAssign: AsmAssignment
init {
if(register!=null && datatype !in NumericDatatypes)
throw AssemblyError("register must be integer or float type")
}
companion object {
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
val idt = inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.typeOrElse(DataType.STRUCT)
when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
memoryAddress != null -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, dt, assign.definingSubroutine(), memory = memoryAddress, origAstTarget = this)
else -> throw AssemblyError("weird target")
}
}
fun fromRegisters(registers: RegisterOrPair, scope: Subroutine?, program: Program, asmgen: AsmGen): AsmAssignTarget =
when(registers) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UBYTE, scope, register = registers)
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
RegisterOrPair.FAC1,
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
}
}
}
internal class AsmAssignSource(val kind: SourceStorageKind,
private val program: Program,
private val asmgen: AsmGen,
val datatype: DataType,
private val variableAsmName: String? = 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?.indexer?.constIndex() }
val asmVarname: String
get() = if(array==null)
variableAsmName!!
else
asmgen.asmVariableName(array.arrayvar)
companion object {
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource {
return when {
indexer.indexNum!=null -> fromAstSource(indexer.indexNum!!, program, asmgen)
indexer.indexVar!=null -> fromAstSource(indexer.indexVar!!, program, asmgen)
else -> throw AssemblyError("weird indexer")
}
}
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
val cv = value.constValue(program)
if(cv!=null)
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
return when(value) {
is NumericLiteralValue -> AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, value.type, number = cv)
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = asmgen.asmVariableName(value))
}
is DirectMemoryRead -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
}
is ArrayIndexedExpression -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
}
is FunctionCall -> {
when (val sub = value.target.targetStatement(program.namespace)) {
is Subroutine -> {
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null }?.first
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
is BuiltinFunctionStatementPlaceholder -> {
val returnType = value.inferType(program)
if(!returnType.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
}
else -> {
throw AssemblyError("weird call")
}
}
}
else -> {
val dt = value.inferType(program)
if(!dt.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
}
}
}
}
fun adjustSignedUnsigned(target: AsmAssignTarget): AsmAssignSource {
// allow some signed/unsigned relaxations
fun withAdjustedDt(newType: DataType) =
AsmAssignSource(kind, program, asmgen, newType, variableAsmName, array, memory, register, number, expression)
if(target.datatype!=datatype) {
if(target.datatype in ByteDatatypes && datatype in ByteDatatypes) {
return withAdjustedDt(target.datatype)
} 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 != DataType.STRUCT) { "must not be placeholder datatype" }
require(source.datatype.memorySize() <= target.datatype.memorySize()) {
"source storage size must be less or equal to target datatype storage size"
}
}
}

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, must be B1+1
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,97 +3,180 @@ package prog8.functions
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.compiler.CompilerException
import kotlin.math.*
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
class FunctionSignature(val pure: Boolean, // does it have side effects?
val parameters: List<BuiltinFunctionParam>,
val returntype: DataType?,
val constExpressionFunc: ConstExpressionCaller? = null)
class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
override fun toString(): String {
val paramConvs = params.mapIndexed { index, it ->
when {
it.reg!=null -> "$index:${it.reg}"
it.variable -> "$index:variable"
else -> "$index:???"
}
}
val returnConv =
when {
returns.reg!=null -> returns.reg.toString()
returns.floatFac1 -> "floatFAC1"
else -> "<no returnvalue>"
}
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
}
}
val BuiltinFunctions = mapOf(
class FSignature(val name: String,
val pure: Boolean, // does it have side effects?
val parameters: List<FParam>,
val known_returntype: DataType?, // specify return type if fixed, otherwise null if it depends on the arguments
val constExpressionFunc: ConstExpressionCaller? = null) {
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
val returns = when(known_returntype) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(known_returntype, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(known_returntype, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(known_returntype, null, true)
in PassByReferenceDatatypes -> ReturnConvention(known_returntype!!, RegisterOrPair.AY, false)
else -> {
val paramType = actualParamTypes.first()
if(pure)
// return type depends on arg type
when(paramType) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(paramType, null, true)
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
else -> ReturnConvention(paramType, null, false)
}
else {
ReturnConvention(paramType, null, false)
}
}
}
return when {
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
actualParamTypes.size==1 -> {
// one parameter? via register/registerpair
val paramConv = when(val paramType = actualParamTypes[0]) {
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
}
CallConvention(listOf(paramConv), returns)
}
else -> {
// multiple parameters? via variables
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
CallConvention(paramConvs, returns)
}
}
}
}
private val functionSignatures: List<FSignature> = listOf(
// this set of function have no return value and operate in-place:
"rol" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"sort" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
"reverse" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("ror" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("rol2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("ror2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
// these few have a return value depending on the argument(s):
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
// normal functions follow:
"sgn" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
"sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FunctionSignature(true, listOf(
BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)),
BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
"rnd" to FunctionSignature(true, emptyList(), DataType.UBYTE),
"rndw" to FunctionSignature(true, emptyList(), DataType.UWORD),
"rndf" to FunctionSignature(true, emptyList(), DataType.FLOAT),
"rsave" to FunctionSignature(false, emptyList(), null),
"rrestore" to FunctionSignature(false, emptyList(), null),
"set_carry" to FunctionSignature(false, emptyList(), null),
"clear_carry" to FunctionSignature(false, emptyList(), null),
"set_irqd" to FunctionSignature(false, emptyList(), null),
"clear_irqd" to FunctionSignature(false, emptyList(), null),
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
"memcopy" to FunctionSignature(false, listOf(
BuiltinFunctionParam("from", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("to", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", StringDatatypes)), DataType.UBYTE, ::builtinStrlen)
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
FSignature("msb" , 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}},
FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
FSignature("rndw" , false, emptyList(), DataType.UWORD),
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
FSignature("exit" , false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
FSignature("rsave" , false, emptyList(), null),
FSignature("rrestore" , false, emptyList(), null),
FSignature("set_carry" , false, emptyList(), null),
FSignature("clear_carry" , false, emptyList(), null),
FSignature("set_irqd" , false, emptyList(), null),
FSignature("clear_irqd" , false, emptyList(), null),
FSignature("read_flags" , false, emptyList(), DataType.UBYTE),
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
FSignature("memcopy" , false, listOf(
FParam("from", IterableDatatypes + DataType.UWORD),
FParam("to", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("memset" , false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UWORD)),
FParam("bytevalue", ByteDatatypes)), null),
FSignature("memsetw" , false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numwords", setOf(DataType.UWORD)),
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
FSignature("strlen" , true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
FSignature("substr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("start", setOf(DataType.UBYTE)),
FParam("length", setOf(DataType.UBYTE))), null),
FSignature("leftstr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
FSignature("rightstr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
FSignature("strcmp" , false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
)
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
val BuiltinFunctions = functionSignatures.associateBy { it.name }
fun builtinMin(array: List<Number>): Number = array.minBy { it.toDouble() }!!
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
@ -121,8 +204,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
if(!idt.isKnown)
throw FatalAstException("couldn't determine type of iterable $arglist")
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
in NumericDatatypes -> dt
in StringDatatypes -> dt
DataType.STR, in NumericDatatypes -> dt
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
}
@ -131,22 +213,22 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
}
val func = BuiltinFunctions.getValue(function)
if(func.returntype!=null)
return InferredTypes.knownFor(func.returntype)
// function has return values, but the return type depends on the arguments
if(func.known_returntype!=null)
return InferredTypes.knownFor(func.known_returntype)
// function has return values, but the return type depends on the arguments
return when (function) {
"abs" -> {
val dt = args.single().inferType(program)
if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
return dt
return if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
dt
else
throw FatalAstException("weird datatype passed to abs $dt")
InferredTypes.InferredType.unknown()
}
"max", "min" -> {
when(val dt = datatypeFromIterableArg(args.single())) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in NumericDatatypes -> InferredTypes.knownFor(dt)
in StringDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
else -> InferredTypes.unknown()
}
@ -159,7 +241,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
DataType.ARRAY_UB, DataType.ARRAY_UW -> InferredTypes.knownFor(DataType.UWORD)
DataType.ARRAY_B, DataType.ARRAY_W -> InferredTypes.knownFor(DataType.WORD)
DataType.ARRAY_F -> InferredTypes.knownFor(DataType.FLOAT)
in StringDatatypes -> InferredTypes.knownFor(DataType.UWORD)
DataType.STR -> InferredTypes.knownFor(DataType.UWORD)
else -> InferredTypes.unknown()
}
}
@ -174,6 +256,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
class NotConstArgumentException: AstException("not a const argument to a built-in function")
class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
@ -228,54 +311,88 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
}
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position)
if(args[0] !is IdentifierReference)
throw SyntaxError("sizeof argument should be an identifier", position)
val dt = args[0].inferType(program)
if(dt.isKnown) {
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
?: throw CannotEvaluateException("sizeof", "no target")
fun structSize(target: StructDecl) =
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position)
return when {
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
numericLiteral(elementDt.memorySize() * length, position)
}
dt.istype(DataType.STRUCT) -> {
when (target) {
is VarDecl -> structSize(target.struct!!)
is StructDecl -> structSize(target)
else -> throw CompilerException("weird struct type $target")
}
}
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
}
}
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
if(argument.type !in StringDatatypes)
throw SyntaxError("strlen must have string argument", position)
throw NotConstArgumentException() // this function is not considering the string argument a constant
val argument=args[0]
if(argument is StringLiteralValue)
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
if(vardecl!=null) {
if(vardecl.datatype!=DataType.STR && vardecl.datatype!=DataType.UWORD)
throw SyntaxError("strlen must have string argument", position)
if(vardecl.autogeneratedDontRemove && vardecl.value!=null) {
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
}
}
throw NotConstArgumentException()
}
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
val constArg = args[0].constValue(program)
if(constArg!=null)
throw SyntaxError("len of weird argument ${args[0]}", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
var arraySize = directMemVar?.arraysize?.size()
var arraySize = directMemVar?.arraysize?.constIndex()
if(arraySize != null)
return NumericLiteralValue.optimalInteger(arraySize, position)
if(args[0] is ArrayLiteralValue)
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
arraySize = target.arraysize!!.size()!!
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}")
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F -> {
arraySize = target.arraysize?.constIndex()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.ARRAY_F -> {
arraySize = target.arraysize!!.size()!!
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
in StringDatatypes -> {
DataType.STR -> {
val refLv = target.value as StringLiteralValue
if(refLv.value.length>255)
throw CompilerException("string length exceeds byte limit ${refLv.position}")
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
}
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
else -> throw CompilerException("weird datatype")
}
}
@ -283,9 +400,9 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 2)
throw SyntaxError("mkword requires lsb and msb arguments", position)
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
throw SyntaxError("mkword requires msb and lsb arguments", position)
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
return NumericLiteralValue(DataType.UWORD, result, position)
}
@ -295,7 +412,7 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toShort(), position)
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
}
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@ -303,7 +420,7 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort(), position)
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
}
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@ -311,7 +428,7 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toShort(), position)
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
}
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@ -319,7 +436,7 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort(), position)
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
}
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@ -358,7 +475,7 @@ private fun builtinSgn(args: List<Expression>, position: Position, program: Prog
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toShort(), position)
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toInt().toShort(), position)
}
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
@ -370,8 +487,8 @@ private fun numericLiteral(value: Number, position: Position): NumericLiteralVal
floatNum
return when(tweakedValue) {
is Int -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
is Short -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
is Int -> NumericLiteralValue.optimalInteger(value.toInt(), position)
is Short -> NumericLiteralValue.optimalInteger(value.toInt(), position)
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)

View File

@ -0,0 +1,87 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
internal class BinExprSplitter(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// TODO somehow if we do this, the resulting code for some programs (cube3d.p8) gets hundreds of bytes larger...:
// if(decl.type==VarDeclType.VAR ) {
// val binExpr = decl.value as? BinaryExpression
// if (binExpr != null && binExpr.operator in augmentAssignmentOperators) {
// // split into a vardecl with just the left expression, and an aug. assignment with the right expression.
// val augExpr = BinaryExpression(IdentifierReference(listOf(decl.name), decl.position), binExpr.operator, binExpr.right, binExpr.position)
// val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
// val assign = Assignment(target, augExpr, binExpr.position)
// println("SPLIT VARDECL $decl")
// return listOf(
// IAstModification.SetExpression({ decl.value = it }, binExpr.left, decl),
// IAstModification.InsertAfter(decl, assign, parent)
// )
// }
// }
// return noModifications
// }
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
/*
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps.
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
X = BinExpr X = LeftExpr
<operator> followed by
/ \ IF 'X' not used X = BinExpr
/ \ IN expression ==> <operator>
/ \ / \
LeftExpr. RightExpr. / \
X RightExpr.
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
return noModifications
if(isSimpleExpression(binExpr.right) && !assignment.isAugmentable) {
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf(
IAstModification.InsertBefore(assignment, firstAssign, assignment.definingScope()),
IAstModification.ReplaceNode(assignment.value, augExpr, assignment))
}
}
// TODO further unraveling of binary expression trees into flat statements.
// however this should probably be done in a more generic way to also service
// the expressiontrees that are not used in an assignment statement...
}
return noModifications
}
private fun isSimpleExpression(expr: Expression) =
expr is IdentifierReference || expr is NumericLiteralValue || expr is AddressOf || expr is DirectMemoryRead || expr is StringLiteralValue || expr is ArrayLiteralValue || expr is RangeExpr
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope) =
if (target.identifier!=null || target.memoryAddress!=null)
target.isInRegularRAM(namespace)
else
false
}

View File

@ -1,27 +1,33 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ErrorReporter
import prog8.ast.base.ParentSentinel
import prog8.ast.base.VarDeclType
import prog8.ast.base.initvarsSubName
import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.loadAsmIncludeFile
private val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq")
)
class CallGraph(private val program: Program): IAstVisitor {
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables
class CallGraph(private val program: Program) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val calls = mutableMapOf<Subroutine, List<Subroutine>>().withDefault { mutableListOf() }
val calledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
val usedSymbols = mutableSetOf<Statement>()
init {
@ -31,9 +37,9 @@ class CallGraph(private val program: Program): IAstVisitor {
fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
fun findSubs(scope: INameScope) {
scope.statements.forEach {
if(it is Subroutine)
if (it is Subroutine)
sub(it)
if(it is INameScope)
if (it is INameScope)
findSubs(it)
}
}
@ -47,17 +53,8 @@ class CallGraph(private val program: Program): IAstVisitor {
it.importedBy.clear()
it.imports.clear()
it.importedBy.addAll(modulesImportedBy.getValue(it))
it.imports.addAll(modulesImporting.getValue(it))
forAllSubroutines(it) { sub ->
sub.calledBy.clear()
sub.calls.clear()
sub.calledBy.addAll(subroutinesCalledBy.getValue(sub))
sub.calls.addAll(subroutinesCalling.getValue(sub))
}
it.importedBy.addAll(importedBy.getValue(it))
it.imports.addAll(imports.getValue(it))
}
val rootmodule = program.modules.first()
@ -65,7 +62,7 @@ class CallGraph(private val program: Program): IAstVisitor {
}
override fun visit(block: Block) {
if(block.definingModule().isLibraryModule) {
if (block.definingModule().isLibraryModule) {
// make sure the block is not removed
addNodeAndParentScopes(block)
}
@ -75,14 +72,16 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(directive: Directive) {
val thisModule = directive.definingModule()
if(directive.directive=="%import") {
val importedModule: Module = program.modules.single { it.name==directive.args[0].name }
modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule)
modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive=="%asminclude") {
if (directive.directive == "%import") {
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val scope = directive.definingScope()
scanAssemblyCode(asm, directive, scope)
val scope = directive.definingSubroutine()
if(scope!=null) {
scanAssemblyCode(asm, directive, scope)
}
}
super.visit(directive)
@ -91,7 +90,7 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(identifier: IdentifierReference) {
// track symbol usage
val target = identifier.targetStatement(this.program.namespace)
if(target!=null) {
if (target != null) {
addNodeAndParentScopes(target)
}
super.visit(identifier)
@ -99,24 +98,18 @@ class CallGraph(private val program: Program): IAstVisitor {
private fun addNodeAndParentScopes(stmt: Statement) {
usedSymbols.add(stmt)
var node: Node=stmt
var node: Node = stmt
do {
if(node is INameScope && node is Statement) {
if (node is INameScope && node is Statement) {
usedSymbols.add(node)
}
node=node.parent
node = node.parent
} while (node !is Module && node !is ParentSentinel)
}
override fun visit(subroutine: Subroutine) {
val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq"),
Pair("prog8_lib", "init_system")
)
if(Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|| subroutine.name== initvarsSubName || subroutine.definingModule().isLibraryModule) {
if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|| subroutine.definingModule().isLibraryModule) {
// make sure the entrypoint is mentioned in the used symbols
addNodeAndParentScopes(subroutine)
}
@ -124,12 +117,12 @@ class CallGraph(private val program: Program): IAstVisitor {
}
override fun visit(decl: VarDecl) {
if(decl.autogeneratedDontRemove || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
// make sure autogenerated vardecls are in the used symbols
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
addNodeAndParentScopes(decl)
}
if(decl.datatype==DataType.STRUCT)
if (decl.datatype == DataType.STRUCT)
addNodeAndParentScopes(decl)
super.visit(decl)
@ -137,10 +130,10 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(functionCall: FunctionCall) {
val otherSub = functionCall.target.targetSubroutine(program.namespace)
if(otherSub!=null) {
if (otherSub != null) {
functionCall.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCall)
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCall)
}
}
super.visit(functionCall)
@ -148,10 +141,10 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(functionCallStatement: FunctionCallStatement) {
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
if(otherSub!=null) {
if (otherSub != null) {
functionCallStatement.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCallStatement)
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(functionCallStatement)
}
}
super.visit(functionCallStatement)
@ -159,10 +152,10 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(jump: Jump) {
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
if(otherSub!=null) {
if (otherSub != null) {
jump.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(jump)
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(jump)
}
}
super.visit(jump)
@ -175,14 +168,12 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(inlineAssembly: InlineAssembly) {
// parse inline asm for subroutine calls (jmp, jsr)
val scope = inlineAssembly.definingScope()
val scope = inlineAssembly.definingSubroutine()
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
super.visit(inlineAssembly)
}
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
private fun scanAssemblyCode(asm: String, context: Statement, scope: Subroutine?) {
asm.lines().forEach { line ->
val matches = asmJumpRx.matchEntire(line)
if (matches != null) {
@ -190,27 +181,30 @@ class CallGraph(private val program: Program): IAstVisitor {
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
val node = program.namespace.lookup(jumptarget.split('.'), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
} else if(jumptarget.contains('.')) {
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
} else if (jumptarget.contains('.')) {
// maybe only the first part already refers to a subroutine
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
if (node2 is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node2)
subroutinesCalledBy[node2] = subroutinesCalledBy.getValue(node2).plus(context)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node2)
calledBy[node2] = calledBy.getValue(node2).plus(context)
}
}
}
} else {
val matches2 = asmRefRx.matchEntire(line)
if (matches2 != null) {
val target= matches2.groups[2]?.value
val target = matches2.groups[2]?.value
if (target != null && (target[0].isLetter() || target[0] == '_')) {
if(target.contains('.')) {
if (target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
if(scope!=null)
calls[scope] = calls.getValue(scope).plus(node)
calledBy[node] = calledBy.getValue(node).plus(context)
}
}
}
@ -218,4 +212,55 @@ class CallGraph(private val program: Program): IAstVisitor {
}
}
}
fun checkRecursiveCalls(errors: ErrorReporter) {
val cycles = recursionCycles()
if(cycles.any()) {
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)
val printed = mutableSetOf<Subroutine>()
for(chain in cycles) {
if(chain[0] !in printed) {
val chainStr = chain.joinToString(" <-- ") { "${it.name} at ${it.position}" }
errors.warn("Cycle in (a subroutine call in) $chainStr", Position.DUMMY)
printed.add(chain[0])
}
}
}
}
private fun recursionCycles(): List<List<Subroutine>> {
val chains = mutableListOf<MutableList<Subroutine>>()
for(caller in calls.keys) {
val visited = calls.keys.associateWith { false }.toMutableMap()
val recStack = calls.keys.associateWith { false }.toMutableMap()
val chain = mutableListOf<Subroutine>()
if(hasCycle(caller, visited, recStack, chain))
chains.add(chain)
}
return chains
}
private fun hasCycle(sub: Subroutine, visited: MutableMap<Subroutine, Boolean>, recStack: MutableMap<Subroutine, Boolean>, chain: MutableList<Subroutine>): Boolean {
// mark current node as visited and add to recursion stack
if(recStack[sub]==true)
return true
if(visited[sub]==true)
return false
// mark visited and add to recursion stack
visited[sub] = true
recStack[sub] = true
// recurse for all neighbours
for(called in calls.getValue(sub)) {
if(hasCycle(called, visited, recStack, chain)) {
chain.add(called)
return true
}
}
// pop from recursion stack
recStack[sub] = false
return false
}
}

View File

@ -54,15 +54,15 @@ class ConstExprEvaluator {
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-bitxor $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -71,15 +71,15 @@ class ConstExprEvaluator {
private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-or $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -88,15 +88,15 @@ class ConstExprEvaluator {
private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-and $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -132,11 +132,11 @@ class ConstExprEvaluator {
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toShort(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
return NumericLiteralValue(DataType.UWORD, left.number.toInt() and right.number.toInt(), left.position)
}
}
throw ExpressionError("cannot calculate $left & $right", left.position)
@ -144,15 +144,15 @@ class ConstExprEvaluator {
private fun power(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot calculate $left ** $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -161,15 +161,15 @@ class ConstExprEvaluator {
private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot add $left and $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -178,15 +178,15 @@ class ConstExprEvaluator {
private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot subtract $left and $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -195,15 +195,15 @@ class ConstExprEvaluator {
private fun multiply(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot multiply ${left.type} and ${right.type}"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
else -> throw ExpressionError(error, left.position)
@ -215,25 +215,25 @@ class ConstExprEvaluator {
private fun divide(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot divide $left by $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteralValue.optimalNumeric(result, left.position)
NumericLiteralValue.optimalInteger(result, left.position)
}
right.type == DataType.FLOAT -> {
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
}
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> {
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
}
right.type == DataType.FLOAT -> {
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
}
@ -245,24 +245,24 @@ class ConstExprEvaluator {
private fun remainder(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute remainder of $left by $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
}
right.type == DataType.FLOAT -> {
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
}
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> {
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
}
right.type == DataType.FLOAT -> {
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
}

View File

@ -1,722 +0,0 @@
package prog8.optimizer
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.fixupArrayDatatype
import prog8.ast.statements.*
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.codegen.AssemblyError
import prog8.functions.BuiltinFunctions
import kotlin.math.floor
class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf()
private val reportedErrorMessages = mutableSetOf<String>()
fun addError(x: AstException) {
// check that we don't add the isSameAs error more than once
if(x.toString() !in reportedErrorMessages) {
reportedErrorMessages.add(x.toString())
errors.add(x)
}
}
override fun visit(decl: VarDecl): Statement {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call tree for this?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
errors.add(ExpressionError("recursive var declaration", decl.position))
return decl
}
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) {
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position)
optimizationsDone++
}
}
else if(decl.arraysize?.size()==null) {
val size = decl.arraysize!!.index.accept(this)
if(size is NumericLiteralValue) {
decl.arraysize = ArrayIndex(size, decl.position)
optimizationsDone++
}
}
}
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)
decl.value = newValue
optimizationsDone++
return super.visit(decl)
}
}
in StringDatatypes -> {
// nothing to do for strings
}
DataType.STRUCT -> {
// struct defintions don't have anything else in them
}
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.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
if(eltType in ByteDatatypes) {
decl.value = ArrayLiteralValue(decl.datatype,
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
decl.value = ArrayLiteralValue(decl.datatype,
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
decl.value!!.linkParents(decl)
optimizationsDone++
return super.visit(decl)
}
}
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.add(ExpressionError("arraysize requires only integers here", numericLv.position))
val size = decl.arraysize?.size() ?: return decl
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.add(ExpressionError("ubyte value overflow", numericLv.position))
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.add(ExpressionError("byte value overflow", numericLv.position))
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.add(ExpressionError("uword value overflow", numericLv.position))
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.add(ExpressionError("word value overflow", numericLv.position))
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
val refValue = ArrayLiteralValue(decl.datatype, array, position = numericLv.position)
refValue.addToHeap()
decl.value = refValue
refValue.parent=decl
optimizationsDone++
return super.visit(decl)
}
}
DataType.ARRAY_F -> {
val size = decl.arraysize?.size() ?: return decl
val litval = decl.value as? NumericLiteralValue
if(litval==null) {
// there's no initialization value, but the size is known, so we're ok.
return super.visit(decl)
} else {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < FLOAT_MAX_NEGATIVE || fillvalue > FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("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(DataType.ARRAY_F, array, position = litval.position)
refValue.addToHeap()
decl.value = refValue
refValue.parent=decl
optimizationsDone++
return super.visit(decl)
}
}
}
else -> {
// nothing to do for this type
}
}
}
return super.visit(decl)
}
/**
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
*/
override fun visit(identifier: IdentifierReference): Expression {
// don't replace when it's an assignment target or loop variable
if(identifier.parent is AssignTarget)
return identifier
var forloop = identifier.parent as? ForLoop
if(forloop==null)
forloop = identifier.parent.parent as? ForLoop
if(forloop!=null && identifier===forloop.loopVar)
return identifier
return try {
val cval = identifier.constValue(program) ?: return identifier
return when {
cval.type in NumericDatatypes -> {
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
copy.parent = identifier.parent
copy
}
cval.type in PassByReferenceDatatypes -> throw AssemblyError("pass-by-reference type should not be considered a constant")
else -> identifier
}
} catch (ax: AstException) {
addError(ax)
identifier
}
}
override fun visit(functionCall: FunctionCall): Expression {
super.visit(functionCall)
typeCastConstArguments(functionCall)
return try {
functionCall.constValue(program) ?: functionCall
} catch (ax: AstException) {
addError(ax)
functionCall
}
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
super.visit(functionCallStatement)
typeCastConstArguments(functionCallStatement)
return functionCallStatement
}
private fun typeCastConstArguments(functionCall: IFunctionCall) {
if(functionCall.target.nameInSource.size==1) {
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
if(builtinFunction!=null) {
// match the arguments of a builtin function signature.
for(arg in functionCall.arglist.withIndex().zip(builtinFunction.parameters)) {
val possibleDts = arg.second.possibleDatatypes
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type !in possibleDts) {
val convertedValue = argConst.cast(possibleDts.first())
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
return
}
}
// match the arguments of a subroutine.
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
for(arg in functionCall.arglist.withIndex().zip(subroutine.parameters)) {
val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.cast(expectedDt)
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
}
}
override fun visit(memread: DirectMemoryRead): Expression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
return super.visit(addrOf.identifier)
return super.visit(memread)
}
/**
* Try to accept a unary prefix expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
*/
override fun visit(expr: PrefixExpression): Expression {
return try {
val prefixExpr=super.visit(expr)
if(prefixExpr !is PrefixExpression)
return prefixExpr
val subexpr = prefixExpr.expression
if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true)
return when {
prefixExpr.operator == "+" -> subexpr
prefixExpr.operator == "-" -> when {
subexpr.type in IntegerDatatypes -> {
optimizationsDone++
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
}
subexpr.type == DataType.FLOAT -> {
optimizationsDone++
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position)
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
}
prefixExpr.operator == "~" -> when {
subexpr.type in IntegerDatatypes -> {
optimizationsDone++
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
prefixExpr.operator == "not" -> {
optimizationsDone++
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
}
else -> throw ExpressionError(prefixExpr.operator, subexpr.position)
}
}
return prefixExpr
} catch (ax: AstException) {
addError(ax)
expr
}
}
/**
* Try to accept a binary expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
*
* More complex stuff: reordering to group constants:
* If one of our operands is a Constant,
* and the other operand is a Binary expression,
* and one of ITS operands is a Constant,
* and ITS other operand is NOT a Constant,
* ...it may be possible to rewrite the expression to group the two Constants together,
* to allow them to be const-folded away.
*
* examples include:
* (X / c1) * c2 -> X / (c2/c1)
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun visit(expr: BinaryExpression): Expression {
return try {
super.visit(expr)
if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
|| expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
throw FatalAstException("binexpr with reference litval instead of numeric")
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
val subExpr: BinaryExpression? = when {
leftconst!=null -> expr.right as? BinaryExpression
rightconst!=null -> expr.left as? BinaryExpression
else -> null
}
if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(program)
val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
// try reordering.
return groupTwoConstsTogether(expr, subExpr,
leftconst != null, rightconst != null,
subleftconst != null, subrightconst != null)
}
}
// const fold when both operands are a const
return when {
leftconst != null && rightconst != null -> {
optimizationsDone++
val evaluator = ConstExprEvaluator()
evaluator.evaluate(leftconst, expr.operator, rightconst)
}
else -> expr
}
} catch (ax: AstException) {
addError(ax)
expr
}
}
private fun groupTwoConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression,
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
subrightIsConst: Boolean): Expression
{
// todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) {
// both operators are the isSameAs.
// If + or *, we can simply swap the const of expr and Var in subexpr.
if(expr.operator=="+" || expr.operator=="*") {
if(leftIsConst) {
if(subleftIsConst)
expr.left = subExpr.right.also { subExpr.right = expr.left }
else
expr.left = subExpr.left.also { subExpr.left = expr.left }
} else {
if(subleftIsConst)
expr.right = subExpr.right.also {subExpr.right = expr.right }
else
expr.right = subExpr.left.also { subExpr.left = expr.right }
}
optimizationsDone++
return expr
}
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
if(expr.operator=="-" || expr.operator=="/") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
val tmp = subExpr.right
subExpr.right = subExpr.left
subExpr.left = expr.left
expr.left = tmp
expr.operator = if(expr.operator=="-") "+" else "*"
expr
} else
BinaryExpression(
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.operator, subExpr.left, expr.position)
} else {
return if(subleftIsConst) {
expr.right = subExpr.right.also { subExpr.right = expr.right }
expr
} else
BinaryExpression(
subExpr.left, expr.operator,
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.position)
}
}
return expr
}
else
{
if(expr.operator=="/" && subExpr.operator=="*") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// C1/(C2*V) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1/(V*C2) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"/",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (C1*V)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
"*",
subExpr.right, expr.position)
} else {
// (V*C1)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="*" && subExpr.operator=="/") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
BinaryExpression(
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1*(V/C2) -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (C1/V)*C2 -> (C1*C2)/V
BinaryExpression(
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// (V/C1)*C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="+" && subExpr.operator=="-") {
optimizationsDone++
if(leftIsConst){
return if(subleftIsConst){
// c1+(c2-v) -> (c1+c2)-v
BinaryExpression(
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1+(v-c2) -> v+(c1-c2)
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (c1-v)+c2 -> (c1+c2)-v
BinaryExpression(
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// (v-c1)+c2 -> v+(c2-c1)
BinaryExpression(
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="-" && subExpr.operator=="+") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// c1-(c2+v) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1-(v+c2) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"-",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (c1+v)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
"+",
subExpr.right, expr.position)
} else {
// (v+c1)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
}
}
return expr
}
}
override fun visit(forLoop: ForLoop): Statement {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
val newFrom: NumericLiteralValue
val newTo: NumericLiteralValue
try {
newFrom = rangeFrom.cast(targetDt)
newTo = rangeTo.cast(targetDt)
} catch (x: ExpressionError) {
return range
}
val newStep: Expression = try {
stepLiteral?.cast(targetDt)?: range.step
} catch(ee: ExpressionError) {
range.step
}
return RangeExpr(newFrom, newTo, newStep, range.position)
}
val forLoop2 = super.visit(forLoop) as ForLoop
// check if we need to adjust an array literal to the loop variable's datatype
val array = forLoop2.iterable as? ArrayLiteralValue
if(array!=null) {
val loopvarDt: DataType = when {
forLoop.loopVar!=null -> forLoop.loopVar!!.inferType(program).typeOrElse(DataType.UBYTE)
forLoop.loopRegister!=null -> DataType.UBYTE
else -> throw FatalAstException("weird for loop")
}
val arrayType = when(loopvarDt) {
DataType.UBYTE -> DataType.ARRAY_UB
DataType.BYTE -> DataType.ARRAY_B
DataType.UWORD -> DataType.ARRAY_UW
DataType.WORD -> DataType.ARRAY_W
DataType.FLOAT -> DataType.ARRAY_F
else -> throw FatalAstException("invalid array elt type")
}
val array2 = array.cast(arrayType)
if(array2!=null && array2!==array) {
forLoop2.iterable = array2
array2.linkParents(forLoop2)
array2.addToHeap()
}
}
// adjust the datatype of a range expression in for loops to the loop variable.
val iterableRange = forLoop2.iterable as? RangeExpr ?: return forLoop2
val rangeFrom = iterableRange.from as? NumericLiteralValue
val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return forLoop2
val loopvar = forLoop2.loopVar?.targetVarDecl(program.namespace)
if(loopvar!=null) {
val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) {
DataType.UBYTE -> {
if(rangeFrom.type!= DataType.UBYTE) {
// attempt to translate the iterable into ubyte values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.BYTE -> {
if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.UWORD -> {
if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.WORD -> {
if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
}
}
return forLoop2
}
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
array.addToHeap()
val vardecl = array.parent as? VarDecl
return if (vardecl!=null) {
fixupArrayDatatype(array, vardecl, program)
} else {
// it's not an array associated with a vardecl, attempt to guess the data type from the array values
fixupArrayDatatype(array, program)
}
}
return array
}
override fun visit(assignment: Assignment): Statement {
super.visit(assignment)
val lv = assignment.value as? NumericLiteralValue
if(lv!=null) {
// see if we can promote/convert a literal value to the required datatype
val idt = assignment.target.inferType(program, assignment)
if(!idt.isKnown)
return assignment
when(idt.typeOrElse(DataType.STRUCT)) {
DataType.UWORD -> {
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
if(lv.type== DataType.UBYTE)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.BYTE && lv.number.toInt()>=0)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.WORD && lv.number.toInt()>=0)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d>=0 && d<=65535)
assignment.value = NumericLiteralValue(DataType.UWORD, floor(d).toInt(), lv.position)
}
}
DataType.UBYTE -> {
// we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255,
if(lv.type== DataType.UWORD && lv.number.toInt() <= 255)
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.BYTE && lv.number.toInt() >=0)
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d >=0 && d<=255)
assignment.value = NumericLiteralValue(DataType.UBYTE, floor(d).toShort(), lv.position)
}
}
DataType.BYTE -> {
// we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127
if(lv.type== DataType.UWORD && lv.number.toInt() <= 127)
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.UBYTE && lv.number.toInt() <= 127)
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d>=0 && d<=127)
assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
}
}
DataType.WORD -> {
// we can convert to WORD: any UBYTE/BYTE, UWORD <= 32767, FLOAT that's an integer -32768..32767,
if(lv.type== DataType.UBYTE || lv.type== DataType.BYTE)
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.UWORD && lv.number.toInt() <= 32767)
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d>=-32768 && d<=32767)
assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
}
}
DataType.FLOAT -> {
assignment.value = NumericLiteralValue(DataType.FLOAT, lv.number.toDouble(), lv.position)
}
else -> {}
}
}
return assignment
}
}

View File

@ -0,0 +1,455 @@
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.*
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
return if(addrOf!=null)
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
else
noModifications
}
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
// Try to turn a unary prefix expression into a single constant value.
// Compile-time constant sub expressions will be evaluated on the spot.
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
val subexpr = expr.expression
if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true)
return when (expr.operator) {
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
"-" -> when (subexpr.type) {
in IntegerDatatypes -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalInteger(-subexpr.number.toInt(), subexpr.position),
parent))
}
DataType.FLOAT -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
parent))
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
}
"~" -> when (subexpr.type) {
DataType.BYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv(), subexpr.position),
parent))
}
DataType.UBYTE -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UBYTE, subexpr.number.toInt().inv() and 255, subexpr.position),
parent))
}
DataType.WORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv(), subexpr.position),
parent))
}
DataType.UWORD -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UWORD, subexpr.number.toInt().inv() and 65535, subexpr.position),
parent))
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
"not" -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
parent))
}
else -> throw ExpressionError(expr.operator, subexpr.position)
}
}
return noModifications
}
/**
* Try to constfold a binary expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
*
* More complex stuff: reordering to group constants:
* If one of our operands is a Constant,
* and the other operand is a Binary expression,
* and one of ITS operands is a Constant,
* and ITS other operand is NOT a Constant,
* ...it may be possible to rewrite the expression to group the two Constants together,
* to allow them to be const-folded away.
*
* examples include:
* (X / c1) * c2 -> X / (c2/c1)
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
val subExpr: BinaryExpression? = when {
leftconst!=null -> expr.right as? BinaryExpression
rightconst!=null -> expr.left as? BinaryExpression
else -> null
}
if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(program)
val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
// try reordering.
val change = groupTwoConstsTogether(expr, subExpr,
leftconst != null, rightconst != null,
subleftconst != null, subrightconst != null)
return change?.let { listOf(it) } ?: noModifications
}
}
// const fold when both operands are a const
if(leftconst != null && rightconst != null) {
val evaluator = ConstExprEvaluator()
val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
return listOf(IAstModification.ReplaceNode(expr, result, parent))
}
return noModifications
}
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
// because constant folding can result in arrays that are now suddenly capable
// of telling the type of all their elements (for instance, when they contained -2 which
// was a prefix expression earlier), we recalculate the array's datatype.
if(array.type.isKnown)
return noModifications
// if the array literalvalue is inside an array vardecl, take the type from that
// otherwise infer it from the elements of the array
val vardeclType = (array.parent as? VarDecl)?.datatype
if(vardeclType!=null) {
val newArray = array.cast(vardeclType)
if (newArray != null && newArray != array)
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
} else {
val arrayDt = array.guessDatatype(program)
if (arrayDt.isKnown) {
val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
if (newArray != null && newArray != array)
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
}
}
return noModifications
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// the args of a fuction are constfolded via recursion already.
val constvalue = functionCall.constValue(program)
return if(constvalue!=null)
listOf(IAstModification.ReplaceNode(functionCall, constvalue, parent))
else
noModifications
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr? {
val fromCast = rangeFrom.cast(targetDt)
val toCast = rangeTo.cast(targetDt)
if(!fromCast.isValid || !toCast.isValid)
return null
val newStep =
if(stepLiteral!=null) {
val stepCast = stepLiteral.cast(targetDt)
if(stepCast.isValid)
stepCast.valueOrZero()
else
range.step
} else {
range.step
}
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications
val rangeFrom = iterableRange.from as? NumericLiteralValue
val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!!
val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) {
DataType.UBYTE -> {
if(rangeFrom.type!= DataType.UBYTE) {
// attempt to translate the iterable into ubyte values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.BYTE -> {
if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.UWORD -> {
if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.WORD -> {
if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
if(newIter!=null)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
}
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val numval = decl.value as? NumericLiteralValue
if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program)
if(!valueDt.istype(decl.datatype)) {
val cast = numval.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
}
}
return noModifications
}
private class ShuffleOperands(val expr: BinaryExpression,
val exprOperator: String?,
val subExpr: BinaryExpression,
val newExprLeft: Expression?,
val newExprRight: Expression?,
val newSubexprLeft: Expression?,
val newSubexprRight: Expression?
): IAstModification {
override fun perform() {
if(exprOperator!=null) expr.operator = exprOperator
if(newExprLeft!=null) expr.left = newExprLeft
if(newExprRight!=null) expr.right = newExprRight
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
if(newSubexprRight!=null) subExpr.right = newSubexprRight
}
}
private fun groupTwoConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression,
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
subrightIsConst: Boolean): IAstModification?
{
// todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) {
// both operators are the same.
// If associative, we can simply shuffle the const operands around to optimize.
if(expr.operator in associativeOperators) {
return if(leftIsConst) {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
else
ShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
} else {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
else
ShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
}
}
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
if(expr.operator=="-" || expr.operator=="/") {
if(leftIsConst) {
return if (subleftIsConst) {
ShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
} else {
IAstModification.ReplaceNode(expr,
BinaryExpression(
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.operator, subExpr.left, expr.position),
expr.parent)
}
} else {
return if(subleftIsConst) {
return ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
} else {
IAstModification.ReplaceNode(expr,
BinaryExpression(
subExpr.left, expr.operator,
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.position),
expr.parent)
}
}
}
return null
}
else
{
if(expr.operator=="/" && subExpr.operator=="*") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1/(C2*V) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1/(V*C2) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"/",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (C1*V)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
"*",
subExpr.right, expr.position)
} else {
// (V*C1)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="*" && subExpr.operator=="/") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
BinaryExpression(
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1*(V/C2) -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (C1/V)*C2 -> (C1*C2)/V
BinaryExpression(
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// (V/C1)*C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="+" && subExpr.operator=="-") {
if(leftIsConst){
val change = if(subleftIsConst){
// c1+(c2-v) -> (c1+c2)-v
BinaryExpression(
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1+(v-c2) -> v+(c1-c2)
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (c1-v)+c2 -> (c1+c2)-v
BinaryExpression(
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// (v-c1)+c2 -> v+(c2-c1)
BinaryExpression(
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="-" && subExpr.operator=="+") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// c1-(c2+v) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1-(v+c2) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"-",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (c1+v)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
"+",
subExpr.right, expr.position)
} else {
// (v+c1)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
return null
}
}
}

View File

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

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