Compare commits

...

465 Commits
v7.4 ... v8.1

Author SHA1 Message Date
6ed5f04970 version 8.1 2022-05-25 20:00:26 +02:00
b459b09b2f vm: fix comparison datatype error; primes.p8 works again 2022-05-24 18:26:07 +02:00
3f5877dbcc vm: fix array iteration 2022-05-23 21:24:36 +02:00
e659b91c4d vm: fix storezm/storezx instructions 2022-05-23 21:01:02 +02:00
e09f054058 vm: implemented in-place bit rotate instructions 2022-05-23 20:30:25 +02:00
b646f50265 vm: implemented in-memory bit shift instructions 2022-05-23 20:15:20 +02:00
0a48ef3030 vm: just use new register instead of trying to (ab)use reg 0 2022-05-22 23:38:46 +02:00
ba614801ee cleanup 2022-05-22 23:11:22 +02:00
fd6eb47e68 added inlining certain trivial non-asm subroutine calls 2022-05-22 20:22:09 +02:00
e69aeb8b98 added warning about shadowing variables 2022-05-22 17:34:08 +02:00
26ea1da146 vm: add in-place bitwise or,and,xor 2022-05-20 20:50:27 +02:00
c9e8c7a290 vm: add in-place division 2022-05-19 23:38:16 +02:00
5e4eb92443 vm: add in-place multiply 2022-05-19 23:18:54 +02:00
461b6499ef vm: add in-place add/sub 2022-05-19 22:54:50 +02:00
c769920b6e vm: fix signed divide 2022-05-19 22:24:57 +02:00
181b98ef9e vm: implemented some self-assign instructions 2022-05-18 22:15:42 +02:00
4e1184a400 vm: added some of the sin cos tables in math.p8 2022-05-17 22:56:00 +02:00
e52d9e3210 vm: split off assignment codegen to its own file 2022-05-17 22:38:31 +02:00
dc6475c91b vm: fixed non-byte array indexing 2022-05-17 18:53:33 +02:00
52f9956e92 clarify use of direct-memory in functions that modify in place such as rol/swap 2022-05-16 22:41:31 +02:00
0bf00d1ca4 c64/c128 targets: perform cleanup at program exit such as re-enabling run-stop key and character set switching. 2022-05-15 16:44:26 +02:00
d1a707df57 fix assigning a pointer (uword) to string not copying the correct memory 2022-05-15 16:10:58 +02:00
4dc9b45297 vm: fixed string comparisons, added missing vm string module 2022-05-13 23:10:13 +02:00
6e31eebfb5 vm: ifElse codegen uses proper branching instructions now 2022-05-12 21:26:17 +02:00
a7df828932 vm: codegen uses INCM/DECM if possible 2022-05-12 19:40:31 +02:00
517cf61d11 vm: limit int instructions to just 2 register args 2022-05-11 22:36:47 +02:00
4be7bc8323 vm: limit float instructions to just 2 register args 2022-05-11 22:09:46 +02:00
74c05d00a9 vm: fix comparison operator codegen for floats 2022-05-11 17:07:21 +02:00
677613d30a vm: expressiongen: use resultRegister arg instead of allocating new leftResultReg 2022-05-11 15:58:55 +02:00
bacba629a5 vm: use shift-one instructions in codegen 2022-05-11 15:50:51 +02:00
14e36f1362 vm: fix assignment to array 2022-05-11 15:26:54 +02:00
d43ad849d1 vm: actually use the store-zero instructions in codegen 2022-05-11 15:18:36 +02:00
627aa61184 clean up subroutine inlining, basis for new try 2022-05-09 15:42:58 +02:00
dad5b17ac8 fix regression compiler crash in string comparison 2022-05-08 13:47:24 +02:00
fef52c0112 automatically convert multi-compare expression (if X==1 or X==2..) to contaiment check if X in [1,2,..] 2022-05-08 13:21:34 +02:00
8c4765b386 vm: support non-unary functions in pipe expressions 2022-05-07 20:42:05 +02:00
7c121bfc01 first steps to support multiple args in pipe expressions 2022-05-07 19:00:47 +02:00
942c5cc04b fix crash when optimizing pipe expression too aggressively 2022-05-07 17:29:36 +02:00
348b3036ff now correctly accepts "xxx" * constexpr (where constexpr is not just a single const number) 2022-05-05 23:21:20 +02:00
09d3451d9d vm: accept %asmbinary (but it is eventually ignored in code execution) 2022-05-05 21:43:31 +02:00
b1a49e5f29 vm: implement rest of float instructions 2022-05-04 22:31:45 +02:00
da01a5b4dc vm: implement float to integer cast, any, all, reverse 2022-05-04 22:08:21 +02:00
3f9cdd9b56 vm: fix mul and div instructions 2022-05-04 01:10:59 +02:00
0f9e87d7bb fixed compiler crash when casting float to integer, fixed float to int cast value error on cx16 2022-05-03 23:43:38 +02:00
0869789214 vm: implement float type casts to integer types 2022-05-02 23:38:32 +02:00
10c8cc35c5 vm: implement float divide multiply sub add 2022-05-02 21:53:43 +02:00
30c2e3e8ff vm: fix comparisons codegen 2022-05-02 21:32:45 +02:00
86cc2f1075 vm: implementing more fp instructions 2022-05-02 21:06:14 +02:00
fa357a450b clarify license 2022-05-02 19:46:08 +02:00
b32641db87 remove syscall() builtin functions
vm code can do this via inline assembly
2022-05-01 00:41:30 +02:00
0ee790969d vm: allow inline "assembly" 2022-04-30 23:24:25 +02:00
7844ace934 vm: implementing floating-point 2022-04-29 22:27:02 +02:00
f4993d6e5d vm: fix instruction type checks 2022-04-28 22:19:46 +02:00
0fab806f36 vm: some preparations for floating point 2022-04-27 17:45:58 +02:00
be2113d291 vm: starting to implement floating point instructions 2022-04-26 21:25:59 +02:00
625d5b2313 vm: some preparations for floating point 2022-04-26 21:08:32 +02:00
6471c0c536 upgrade antlr to 4.10.1 2022-04-24 23:29:15 +02:00
47c53fa60a todo 2022-04-23 20:44:59 +02:00
cf50e4f6ec vm: printing of numbers now via conv module.
assigning strings now converted to strcopy function call in the compiler ast.
2022-04-23 02:15:51 +02:00
7eea97d741 - floats: remove all floating point builtin functions and move them to the floats module instead 2022-04-22 00:45:54 +02:00
88b55ab93e vm: add abs() and fix 6502 abs() code. 2022-04-18 21:20:17 +02:00
ee36d47c27 vm: added cmp() and most of the status-branch instructions 2022-04-18 19:59:48 +02:00
6f2fdbe447 added %option merge, also fixed problem with unit test building in newer IntelliJ version 2022-04-15 22:38:32 +02:00
0f36be0001 vm: simple optimizations for +/-/*/div with constants 2022-04-14 22:42:25 +02:00
0f4a197e34 improve ast check on pipe expressions 2022-04-14 00:49:06 +02:00
7dbff5b9e6 abs: remove support for floats. Use floats.fabs() instead.
this solves: can't use abs() etc in pipe expression because return type depends on argument type
2022-04-14 00:38:31 +02:00
220246278a removed sum(), max(), min(). abs() now always returns uword type.
This greatly simplifies internal handling of builtin functions by always having one fixed return type.
2022-04-14 00:21:16 +02:00
349e5a15e9 min/max give proper error for string args
als implmented more vm builtin functions/syscalls
2022-04-13 23:09:25 +02:00
bf7f4bba7b doc 2022-04-13 20:43:07 +02:00
ab1766a559 moved all *integer* builtin trig functions (sin8u, cos8u etc) as regular asmsubs in math module 2022-04-13 00:27:35 +02:00
51bf33040a vm: add many builtin functions 2022-04-11 22:39:33 +02:00
a2c7273801 vm: use memory load instruction better 2022-04-11 20:55:06 +02:00
ec6ac5bf24 vm: added swap() 2022-04-11 01:50:47 +02:00
ec7501782d vm: added 1-bit variants of lsr/lsl opcodes 2022-04-11 00:25:00 +02:00
890b1c2d52 more readable 2022-04-10 22:31:37 +02:00
c25d07259a add block directive options to PtBlock 2022-04-10 21:37:47 +02:00
c960246eee add some utility methods to PtNode to find the defining subroutine/block 2022-04-10 21:20:01 +02:00
a01aee3111 add sideEffects boolean to PtBuiltinFunctionCall 2022-04-10 21:08:54 +02:00
e2e951efdf constValue(expr) convenience function added for new Ast expression nodes 2022-04-10 18:45:33 +02:00
3f6393f732 PtNumber can now be compared 2022-04-10 17:48:03 +02:00
b6eb343234 moving string escaping out of antlr project 2022-04-10 17:31:30 +02:00
207a7e5160 move operator lists 2022-04-10 13:24:17 +02:00
a0face4a28 vm: implementing rol/ror 2022-04-09 11:13:49 +02:00
a8cf9f5cc4 vm: syscalls can now return value 2022-04-05 20:46:34 +02:00
461b38e653 add -vm option to load an existing p8virt file directly in the virtual machine 2022-04-05 18:42:31 +02:00
8e4c0f7c22 vm: add sorting and reverse functions, fix value arg out of range errors 2022-04-05 17:48:49 +02:00
d78bfcc35c vm: more optimal code when array index is constant value 2022-04-05 00:19:37 +02:00
2b7c09e6ee vm: more optimal code for loops ending on 0 2022-04-05 00:08:38 +02:00
036d9dbe59 got rid of unnecessary cast of boolean expressions by making their type dynamically adjust to byte or word 2022-04-04 23:43:55 +02:00
1d342cc6af optimize cx16 textio.setcc()/setcc2() 2022-04-04 22:23:06 +02:00
62b32b2211 todos 2022-04-03 22:56:13 +02:00
ae45ce517e cleanups 2022-04-03 17:33:50 +02:00
5b3ccab7dc vm: support noreinit option 2022-04-03 17:19:50 +02:00
95f16c38a9 removed 'aug' property in PtAssignment , it wasn't used for anything 2022-04-03 15:56:14 +02:00
d616cb283b vm: implemented Pipe expression 2022-04-03 15:25:32 +02:00
9874fe2c23 fix superfluous printing of WARN/ERROR words 2022-04-02 22:16:47 +02:00
520a142992 version 8.0 2022-04-02 19:10:18 +02:00
6ff56dc0bb vm: implemented When 2022-04-02 19:04:12 +02:00
1e63615592 tweaks 2022-04-02 18:04:41 +02:00
3e62ffed0a x16 r39: optimize diskio.load_raw() to use kernal's headerless load support 2022-04-02 03:26:48 +02:00
b133d51a83 make the parser report '&&' as an error instead of treating it as bitwise and followed by address-of. 2022-04-02 02:08:01 +02:00
037b89f018 x16 r39: tweak kbdbuf_peek() result value 2022-04-01 23:47:09 +02:00
20d06d9f9d fix return type error for asmsubs with >1 result values 2022-04-01 22:30:15 +02:00
156cf7315c x16 r39: add new keyboard apis and more vector location definitions 2022-04-01 21:41:38 +02:00
e2886e5303 x16 r39: update vtui lib and example 2022-04-01 21:09:40 +02:00
c6cf330e70 fix bug in codegen for containment check in bytearrays and strings 2022-04-01 20:46:28 +02:00
6be3b62d78 fix new Ast gosub node translation 2022-04-01 18:07:06 +02:00
c57af5e81b todo 2022-04-01 01:10:13 +02:00
f7431f809e fix colorbars example issue with memcopy due to overlapping buffers 2022-04-01 00:54:06 +02:00
ea43c34de8 x16 r39: fix screen colors after changing screen mode 2022-04-01 00:21:22 +02:00
fb6e9fa58f x16 r39: fix textio routines for new vera memory layout 2022-03-31 23:47:11 +02:00
b2ce1e8029 x16 r39: fix gfx2.text() charset rendering due to new Vera mem layout 2022-03-31 18:51:54 +02:00
d90c51220f x16 r39: additional FP routine changes 2022-03-31 18:43:32 +02:00
d1b14b68fa x16 r39: more free ZP registers possible by enabling floatsafe option 2022-03-31 18:30:26 +02:00
d911728611 x16 r39: cx16.mouse_config() API changed, added mouse_config2() convenience wrapper 2022-03-31 18:24:23 +02:00
86a7200012 x16 r39: cx16.screen_set_mode() -> cx16.screen_mode() 2022-03-31 18:17:28 +02:00
6ddb7453e1 vm postincrdecr on array done 2022-03-31 02:13:20 +02:00
ad2355f8d3 vm forloop done 2022-03-31 01:41:59 +02:00
582c498fe3 major version bump because upcoming breaking changes in cx16 r39 release target (kernal routines) 2022-03-31 00:12:26 +02:00
0a0c58d450 added for loop over constant ranges 2022-03-30 23:42:15 +02:00
0dc592b819 working on vm 2022-03-30 22:23:25 +02:00
f46300016d working on vm 2022-03-30 01:58:31 +02:00
3e1a7c6102 fix vm signed comparisons 2022-03-29 00:57:33 +02:00
f07065bf84 some x16 example changes to use the improved mode $80 screen resolution 2022-03-28 22:30:34 +02:00
6d79903eb3 workin on vm issues 2022-03-28 01:49:43 +02:00
e166329f34 fix error for certain typecasted expressions inside comparisons 2022-03-28 01:38:01 +02:00
bb1bf6a88c working on vm 2022-03-28 00:40:15 +02:00
30cbb6c9a8 implementing more of the vm 2022-03-27 21:59:46 +02:00
4e33ab1e89 cx16 target: update float routine addresses to new r39 kernal FP package 2022-03-27 19:34:49 +02:00
5494f309c0 working on vm 2022-03-27 17:46:15 +02:00
3b6e7eccdd simplified containment check, only possible on string and arrays (as per the docs) 2022-03-27 16:59:55 +02:00
e41d6787bb working on vm 2022-03-27 14:23:01 +02:00
ed30108961 removed '**' power-operator. Use floats.pow() instead. 2022-03-27 13:16:34 +02:00
12712ef812 working on vm 2022-03-27 11:48:44 +02:00
0307f6b42c working on vm 2022-03-25 20:22:41 +01:00
3e44620966 add unit test for the string encoders special handling of 0x0000 and 0x8000-0x80ff 2022-03-25 18:26:23 +01:00
7424f1f768 remove kernal bug workaround for joystick_get() routine. Fixes #39 2022-03-25 18:03:33 +01:00
b5331d821c fix string encoding for escaped characters 2022-03-25 00:17:41 +01:00
27f6d47efa working on vm codegen 2022-03-24 23:26:57 +01:00
dbc7ad2ec4 no more Gosub node in new Ast, back to Functioncalls there. 2022-03-22 22:48:19 +01:00
7b27d270a2 gosub only uses an identifier 2022-03-22 20:53:06 +01:00
97b3a0b093 don't use the temp-variables introducing optimizations for the vm target 2022-03-22 20:21:32 +01:00
06b38506d1 working on vm translator 2022-03-22 01:43:02 +01:00
fd581ffc37 moved pattern_match() from prog8_lib to string module 2022-03-21 21:32:10 +01:00
ff57c5e9d3 working on vm and new ast 2022-03-21 01:36:11 +01:00
9b16d7c786 working on vm 2022-03-20 15:06:29 +01:00
4c1bb18956 refreshrate default value 2022-03-19 01:08:10 +01:00
7d2bf892b1 added start of virtual machine compilation target 2022-03-19 00:57:35 +01:00
a99e77093f added syscall() builtin functions (only useful for experimental code gen) 2022-03-17 01:19:58 +01:00
92737bb695 better handling of loadAddress 2022-03-13 16:21:02 +01:00
9b81955544 optimizing new Ast 2022-03-13 11:49:07 +01:00
4a0031080a getting rid of directives in new Ast 2022-03-13 00:30:20 +01:00
40e9fba312 working on new Ast and XML export to test it 2022-03-12 22:38:16 +01:00
e227cc92ff new ast: regular subroutine has just 0 or 1 return type 2022-03-12 14:12:06 +01:00
73dbdbcbe6 module rename 2022-03-11 21:24:16 +01:00
3961f26635 consolidating modules 2022-03-11 20:45:39 +01:00
e51c274a18 reducing dependencies 2022-03-11 20:32:35 +01:00
e75d0c58a9 reducing dependencies 2022-03-10 23:46:43 +01:00
9a798360f4 introduced codeAst and codeCore modules to reduce dependencies 2022-03-10 22:38:16 +01:00
844ad09464 reducing dependencies 2022-03-10 21:36:51 +01:00
1e1d1efd90 reducing dependencies 2022-03-10 21:23:01 +01:00
240e6835c2 decide sim is not worth it-remove it again 2022-03-10 21:23:01 +01:00
61398ee8f8 decide sim is not worth it 2022-03-10 21:23:01 +01:00
e6e84859b7 building more of the simulator 2022-03-10 21:23:01 +01:00
abcdd331db started with a simulator 2022-03-10 21:23:00 +01:00
775d136b91 new compileText result 2022-03-10 21:22:32 +01:00
dc93691fd9 working on new ast 2022-03-10 21:22:32 +01:00
48d782c69c added flat mapping to symboltable 2022-03-10 21:22:31 +01:00
0a04e626d7 added new intermediate (simplified) AST meant for new codegen 2022-03-10 21:21:15 +01:00
e7c4bf5ebf reducing dependencies 2022-03-10 21:17:31 +01:00
546a416f7e reducing dependencies 2022-03-10 20:57:36 +01:00
179a7a2792 reducing dependencies 2022-03-10 02:17:06 +01:00
251b6fcf70 reducing dependencies 2022-03-10 02:09:34 +01:00
ab1fffb721 reducing dependencies 2022-03-10 01:41:42 +01:00
da352a322c reducing dependencies 2022-03-10 01:27:27 +01:00
7d20458e82 fixed arrayliteral regression 2022-03-10 01:02:40 +01:00
5a54066f81 unravel more dependency of SymbolTable on the ASt nodes (Expression), and fix initializing zp-allocated array 2022-03-09 01:42:05 +01:00
a58e5a3399 simplify result handling of assembly phase 2022-03-08 18:51:07 +01:00
9872f43cbf repeat-forever loop is now replaced by label+jump 2022-03-08 03:25:34 +01:00
1078cc4642 remove debug 2022-03-07 21:45:29 +01:00
db7ae028b2 simplified CompilationResult a bit 2022-03-07 21:41:12 +01:00
2b6f5dbd59 cleanup st use at variable asm generation 2022-03-06 19:50:15 +01:00
f7aa0c45df optimize imports 2022-03-05 15:54:42 +01:00
a72d58cdf9 updated 3rd party library versions 2022-03-05 15:28:22 +01:00
067283834a got rid of old IVariablesAndConsts object 2022-03-05 14:40:41 +01:00
cf362c4a61 getting rid of old IVariablesAndConsts object 2022-03-05 14:11:58 +01:00
496245c801 working on symboltable 2022-03-05 12:10:20 +01:00
859ab36347 variables extraction moved to the very end, so no need anymore to change the table after the fact 2022-03-04 23:12:24 +01:00
1d740c7c36 removed need to store ast scope on each zp allocated var, now uses scoped name to find them 2022-03-04 22:58:05 +01:00
a03c4c3659 working on symbol table 2022-03-04 22:26:46 +01:00
094ecceaac fix bug where non-inlined asmsub didn't always get a proper RTS, causing program crash
was caused by a change in 7.8; 8ae3bad6f7 "fix rts in empty asmsub"
2022-03-03 01:10:33 +01:00
2812736ae5 preparing version 7.9 2022-03-03 00:42:53 +01:00
6f87f8706c can only call unary functions in pipe at this time 2022-03-02 23:16:40 +01:00
38beebe720 fix pipe check for number of args 2022-03-02 21:29:09 +01:00
fc1c3c6808 working on altered pipe syntax 2022-03-02 20:58:38 +01:00
96ba895b84 working on altered Pipe syntax 2022-02-27 02:42:28 +01:00
df35dfe3bf added atari XEX output format with default $2000 load address 2022-02-26 15:36:22 +01:00
c5504c6657 added ATASCII encoding table for atari 2022-02-25 23:48:39 +01:00
530e109433 added altirra as atari emu2 2022-02-25 19:16:37 +01:00
6cce47b2f1 fix launching emulator for atari target 2022-02-24 23:22:02 +01:00
6185d5eca1 Merge remote-tracking branch 'origin/master' 2022-02-24 22:52:08 +01:00
685ad1746e Merge pull request #74 from FreddyOffenga/master
temporary fix for chrout and newline for atari and added two examples
2022-02-24 22:30:59 +01:00
891f870ec0 todo 2022-02-23 21:58:27 +01:00
ad9933f0f6 fixed chrout for atari and added two examples 2022-02-23 16:42:22 +01:00
1b86117754 todo 2022-02-22 23:38:09 +01:00
eeb3c968d6 streamline handling of launcher type and program load address. %address is now required if not using a basic-launcher. 2022-02-22 22:43:14 +01:00
406658a10f reimplemented sys.memcopy and sys.memset on cx16 to work without kernal too 2022-02-22 21:07:19 +01:00
6a0551cea1 added 'atari' compiler target beginnings (Atari 800 XL)
also default char and string encoding now taken from compiler target
2022-02-22 00:52:35 +01:00
553f3b45d2 compile time calculated values of sin/cos routines fixed to be identical to the results of the run-time functions 2022-02-21 21:30:42 +01:00
064a8e785c cleanups 2022-02-21 03:26:17 +01:00
21e9723bb2 allow the last term in a pipe statement to be a variable, rewrites this as var = <rest of pipe> 2022-02-21 02:33:19 +01:00
60b2c44a44 fix returntype handling of builtinfunctions, fix errors in pipe expressions 2022-02-21 01:44:29 +01:00
c4fe3ecc0a refactor 2022-02-20 22:04:18 +01:00
2f18a8f6d0 introduced BuiltinFunctionCall (expression) node for codegen 2022-02-20 02:48:27 +01:00
5ac784e18a cleanup 2022-02-19 00:30:59 +01:00
7a2164b4d0 introduced BuiltinFunctionCallStatement node for codegen 2022-02-18 23:27:11 +01:00
0a43eae184 rework registerArgsViaStackEvaluation to use cpu hardware stack instead 2022-02-18 22:38:00 +01:00
3117e2b2a3 more tweaks 2022-02-18 01:25:08 +01:00
41fece4643 slight tweaks related to builtin functions in the ast 2022-02-17 01:25:13 +01:00
7aa807ec7f proper error if attempting to do a containment check against non const range, and some cleanup in asmgen 2022-02-16 00:39:19 +01:00
4d16e1e14a now checks for invalid text encodings for given compilation target 2022-02-15 01:39:12 +01:00
73fc18099e properly report duplicate label names 2022-02-15 00:39:10 +01:00
e34dac8dbb remove unit test issue 2022-02-15 00:38:51 +01:00
af0e7f7187 searching names in inlined assembly now ignores source comments 2022-02-13 13:41:12 +01:00
a3a6812608 version 7.8 2022-02-12 17:40:32 +01:00
2725c4ad4d slight tweaks to zp and allocator 2022-02-12 00:15:52 +01:00
c8cd6e9460 removed old @"screencodes" string encoding syntax (use sc:"hello" instead) 2022-02-11 22:07:14 +01:00
0cd27d6129 fix empty lines in subroutine ast printing 2022-02-11 21:44:38 +01:00
b47fc1c020 renames of some Ast node classes 2022-02-11 00:34:36 +01:00
de6ef7ef5e doc 2022-02-11 00:16:39 +01:00
f95fe8f1da note about removing VarDecls 2022-02-10 23:20:19 +01:00
bd0dee5db5 cleanup 2022-02-10 22:22:50 +01:00
c13b7fd883 report free/occupied Zeropage space at end of compilation 2022-02-10 21:59:44 +01:00
f7e74b3088 naming 2022-02-10 03:18:56 +01:00
343f01d5e1 re-enabled unused variable removal from library modules (+fixed some @shared vars in libraries) 2022-02-10 03:10:47 +01:00
08bacdd090 temp vars are now dynamically added to AST as needed 2022-02-10 02:52:47 +01:00
41b1c80492 label name from memory() no longer interned as string var 2022-02-10 00:45:20 +01:00
e5d7316e5d streamlining non-zpvars asmgen using new mechanism 2022-02-10 00:09:09 +01:00
b043c3a6da streamlining vars asmgen using new mechanism 2022-02-09 21:58:25 +01:00
98b2855b9c cleanups 2022-02-09 16:35:52 +01:00
f3c52c409f variable zp allocation now only done in the allocator 2022-02-08 23:44:21 +01:00
1307bdc612 more cleanups to the allocator 2022-02-08 22:46:49 +01:00
8c2e6971fc start using vars instead of callgraph (2) 2022-02-08 21:09:00 +01:00
1903990f30 start using vars instead of callgraph 2022-02-08 20:40:10 +01:00
7d67005709 more rewrite variable allocation 2022-02-08 20:40:10 +01:00
9acc2f92d1 start to rewrite variable allocation 2022-02-08 20:40:10 +01:00
72dfb0bda3 fix: undefined sys.memcopy when initializing array on cx16 2022-02-08 20:29:47 +01:00
1635612430 tiny tweak in asm optimizer 2022-02-08 02:19:50 +01:00
abda837d2f split program structure codegen out of AsmGen into separate class ProgramGen 2022-02-07 00:12:25 +01:00
101fb0b8aa some naming changes and cleanups 2022-02-06 23:14:44 +01:00
10de7dc1f9 fixed the concurrent modification issue on zeropage when running unit tests in parallel, by not having machine targets be static objects 2022-02-06 21:29:06 +01:00
d2309b8114 introducing IVariableAllocation (WIP) 2022-02-06 18:57:23 +01:00
6bdd81623f cleaning up AsmGen interface 2022-02-06 17:07:03 +01:00
f538c9f0c3 remove bogus double var decl check 2022-02-06 14:04:54 +01:00
8ae3bad6f7 fix rts in empty asmsub 2022-02-06 05:05:58 +01:00
77de99b383 rts-check for non-inlined subroutines + var init adjustment when noreinit, moved out of codegen 2022-02-06 04:03:03 +01:00
312949f336 added experimental codegen backend option 2022-02-05 21:42:03 +01:00
1ab635bd7e small tweak of parse messages 2022-02-05 14:02:24 +01:00
b35abd548c less noisy output about what module files are being imported. 2022-02-05 04:25:34 +01:00
30e1c3307c simplify SourceCode: just read the full text immediately. Also optimized imports. 2022-02-05 03:50:54 +01:00
08e052380a comments 2022-02-05 03:14:26 +01:00
0e824c35cc Merge pull request #73 from akumanatt/master
Codegen and runtime library optimizations
2022-02-05 02:21:53 +01:00
548374ac2d fix: do proper sign exension when multiplying signed word and byte vars 2022-02-05 01:52:13 +01:00
9ad79fefc9 Merge branch 'master' of https://github.com/irmen/prog8 2022-02-04 22:55:41 +07:00
49d37c016e Optimize strcmp_mem 2022-02-04 22:07:03 +07:00
7c70c79a84 Optimize in-place word subtraction and negation 2022-02-04 21:21:06 +07:00
6916b8bff7 remove redundant properties 2022-02-03 23:59:24 +01:00
73dfb5f443 Optimize sign extension to AY 2022-02-04 00:59:44 +07:00
69b9dfa468 fix invalid recursion warning for code referencing subroutine but not via a call 2022-02-01 23:09:52 +01:00
ab61b8ba0a doc ref 2022-02-01 21:47:53 +01:00
5c8c64242f callgraph: nameInAssemblyCode searches smarter (for unused()) 2022-02-01 00:33:05 +01:00
ddf96943f0 remove Nop ast node. 2022-01-31 22:36:10 +01:00
e773be2f58 remove no longer needed asmSymbol scoping prefixing, now asmSymbolName are identical to asmVarName 2022-01-31 01:47:22 +01:00
f965804e6d fix invalid optimization of returning a parameter variable in a subroutine 2022-01-28 16:44:42 +01:00
ec078eba72 optimize w=msb(w) => w>>=8, w=lsb(w) ==> w&=$00ff 2022-01-28 16:11:52 +01:00
1815cb1bc3 fixed bug in assembly optimizer removing too many instructions 2022-01-28 15:19:08 +01:00
7b3cd71085 fixed improper optimization of word<<8 and word>>8 2022-01-28 13:54:06 +01:00
06128b5d07 optimize word&=$ff00 and word&=$00ff 2022-01-28 13:40:28 +01:00
990c8e1f18 split out 6502 codegen module from various compilertargets module. 2022-01-28 00:32:09 +01:00
a170506356 simplify IdentifierReference equality check back to default (name+pos) 2022-01-27 23:32:55 +01:00
5ecf2a3357 enable more optimizations for typecasted assignments. Fixed missing codegen for assigning bytes to words in certain cases. 2022-01-27 18:05:25 +01:00
fa48746ba9 increase internal buffer for diskio.list_files to be able to list larger directories 2022-01-26 03:17:33 +01:00
e2b8c069d7 check for missing '&' in string + value expressions (can't just add a value to a string) 2022-01-24 23:30:40 +01:00
14407bd1aa fix memory() existing check typo 2022-01-24 23:21:31 +01:00
08db72903c for long containment checks use a subroutine instead of huge cmp-table 2022-01-24 22:40:22 +01:00
46f9fab140 library API change: string.find now returns index of character + carry bit status (instead of substring address) 2022-01-24 21:37:04 +01:00
b7d06f2c0a API change: added alignment parameter to memory() function 2022-01-24 18:58:57 +01:00
118196a0bf library API change: moved cx16.vload() to cx16diskio module 2022-01-24 18:31:18 +01:00
586ce1fc80 tweak return's use of intermediate variable 2022-01-24 01:10:04 +01:00
adb979df38 tweak comment 2022-01-23 22:34:05 +01:00
3401cb5b4a fixed compiler recursion crash when returning certain typecasted value 2022-01-23 19:13:20 +01:00
ebf1f12e97 inferred type for len() is now more precise 2022-01-23 17:24:39 +01:00
5766208207 fix compiler crash when initializing an array var with another array var 2022-01-23 14:23:34 +01:00
4bf4771f08 fix @requirezp in astToSource. Fix sometimes allocating zeropage variables in normal ram. 2022-01-23 13:42:52 +01:00
0e87db9eb7 fix invalid size copied when initializing arrays in Zeropage 2022-01-23 13:00:01 +01:00
1e053783f3 fix invalid size copied when assigning non-byte arrays 2022-01-23 02:42:36 +01:00
7afc96112b now correctly requires using & (address-of) when assigning the address of a label or subroutine, used to generate invalid code when it was omitted 2022-01-23 02:23:30 +01:00
7bb41a30ed fixed compiler crash when assigning number larger than 65535 2022-01-23 01:44:16 +01:00
3d1b0eb843 fixed compiler crash when using cx16.r0H as function call argument 2022-01-23 01:28:16 +01:00
5b9af0b5ae tweaks 2022-01-21 23:38:54 +01:00
9219ec539d allow "goto pointervar" for indirect jumps 2022-01-21 22:55:59 +01:00
c8bd57cd4d fixed signature of mouse_get(): it returns the buttonstatus in A. Added convenience cx16.mouse_pos() routine. 2022-01-21 22:06:17 +01:00
53bf8c09fd fix screencode encoding selection 2022-01-19 21:37:27 +01:00
651c383668 refactor encoder to be the same for all 3 machine targets now 2022-01-19 21:21:33 +01:00
9ed7587e3e document new string encoding syntax 2022-01-19 21:21:33 +01:00
674295e800 improve error reporting from string encoders 2022-01-19 21:21:33 +01:00
6b02f2eea0 implement iso encoding and new string encoding syntax, fixes #38 2022-01-19 21:21:32 +01:00
5237e55326 added txt.iso() to enable iso-charset on cx16 2022-01-18 21:35:29 +01:00
3b59592110 generalize string encoding flag into enum 2022-01-18 21:21:49 +01:00
72640ae058 no longer add nops around breakpoint for vice 2022-01-17 22:12:58 +01:00
d916027e75 labels no longer start with '_' fixes #62 2022-01-17 22:03:53 +01:00
8966d2aa06 comments and prepare new version 7.7 2022-01-16 23:03:00 +01:00
de7ea04f54 when zp option = dontuse, print error for any variable with @requirezp 2022-01-16 18:13:24 +01:00
bf71fabe0e fix codegen mistake for zp allocated loop vars 2022-01-16 18:09:09 +01:00
b3368acb33 todos to fix broken examples 2022-01-16 17:57:47 +01:00
87220c6697 docs for @requirezp 2022-01-16 17:20:36 +01:00
a3b5c2ad71 fix zp address output and adjust vars datastructure 2022-01-16 17:20:36 +01:00
fb4c1473c5 array and string initialization in zeropage 2022-01-16 17:20:36 +01:00
2bb2502d20 added @requirezp to syntax files 2022-01-16 17:20:36 +01:00
fe51698579 tweak how zp varnames are stored 2022-01-16 17:20:36 +01:00
0f0f40bff3 improved ForloopAsmGen loopvar zp allocation 2022-01-16 17:20:36 +01:00
a798fe72d3 cx16 reserved zp vars (virtual registers) 2022-01-16 17:20:36 +01:00
fba98d03a5 improve %zpreserved error messages 2022-01-16 17:20:36 +01:00
7dd2517f67 fix Zp allocation issues 2022-01-16 17:20:36 +01:00
641477d6f6 add @requirezp and allow str/array to be on zp (with warning) 2022-01-16 17:20:32 +01:00
8e56656c8d fix broken code generated for certain ==/!= expressions 2022-01-16 17:10:49 +01:00
564a6a1f62 fix invalid removal of Jump
that would generate wrong code if occurs at the end of a subroutine
2022-01-16 14:05:42 +01:00
69f0c80cd7 added pokemon() function 2022-01-15 19:04:04 +01:00
6fcb51cea2 add warning when encoded string contains 0-byte 2022-01-15 17:11:40 +01:00
c58b8a4973 fix ast to source: @shared wasn't printed
fix grammar: @shared and @zp can occur in any order now in vardecl
2022-01-13 02:29:55 +01:00
c8f4ab4f06 doc 2022-01-12 22:21:01 +01:00
e425c4cca8 optimizing pipe codegen 2022-01-11 23:17:35 +01:00
056ec986c2 use var initializer assignments in a clearer way 2022-01-11 00:34:44 +01:00
de3b2fb95b slightly optimized certain list iterations into sequences 2022-01-10 23:15:24 +01:00
789e39c719 slightly optimized assembly file handling 2022-01-10 22:48:20 +01:00
b29c3152db Assignment: make its origin explicit 2022-01-10 02:25:02 +01:00
3831679772 VarDecl: make its origin explicit 2022-01-10 01:53:03 +01:00
596f9566d8 todo 2022-01-10 01:00:50 +01:00
124befe9d6 slightly optimized code for assigning boolean expressions such as `b = xx>99` 2022-01-09 18:49:44 +01:00
895534f32b don't remove dead variable assignments if they are a function call 2022-01-09 18:41:01 +01:00
50c16fe6de code size optimization: don't copy floats with inlined copy code but use copy_float routine 2022-01-09 16:18:13 +01:00
b092d1a5d3 fixed code gen issue for uword >= comparison 2022-01-09 13:23:29 +01:00
a9b45630d7 optimized code for variable comparisons to zero 2022-01-09 13:10:01 +01:00
c1a39c269e optimized code for stack eval comparisons with zero 2022-01-09 03:19:49 +01:00
6fa3f0b6cd small refactor 2022-01-08 18:02:38 +01:00
9e5e3d1559 pipe expression not evaluated via stack 2022-01-08 17:51:23 +01:00
7135205299 fix codegen bug for pipe expressions to actually return correct value and not corrupt X register 2022-01-08 17:41:46 +01:00
d99d977d2b fix more typecasting issues 2022-01-08 17:04:25 +01:00
7dd7e562bc pipes also as expressions, cleanup codegen, fix various typecasting issues 2022-01-08 13:45:19 +01:00
75d857027e cleanup of Pipe codegen 2022-01-08 01:33:40 +01:00
17694c1d01 better error handling of invalid number casts 2022-01-07 22:12:13 +01:00
749ad700d8 naming consistency for some expression classes 2022-01-07 21:02:55 +01:00
8f3df3039a added pipe operator `|>` 2022-01-06 22:54:18 +01:00
02c315c194 add missing unit tests and type checking for 'in' expression 2022-01-06 00:01:49 +01:00
b697375573 add note about unspecified order of evaluation of expressions and subroutine call arguments 2022-01-05 23:21:45 +01:00
c57ef7725e preparing v7.6 2022-01-04 20:40:35 +01:00
3ae07503f2 doc css styling: font size slightly bigger for code as well 2022-01-03 23:28:11 +01:00
9a0341adde doc css styling: font size slightly bigger 2022-01-03 23:16:07 +01:00
96225efd96 library doc tweaks 2022-01-03 23:15:34 +01:00
c3bd904f41 todo 2022-01-02 23:46:36 +01:00
74257163b1 fix that memory("name", ...) also allocates a STR variable for the name 2022-01-02 17:07:04 +01:00
7bc75fd220 fix that memory("a b c", ...) produces invalid symbol 2022-01-02 16:11:53 +01:00
a23281afab added experimental -noreinit option 2022-01-01 16:35:36 +01:00
9e90dbdde6 slight optimization of repeat loop (> 256 iters) code generation on 65c02 cpu 2022-01-01 14:42:37 +01:00
1e8d8e40a2 slight optimization of repeat loop (0-256 iters) code generation on 65c02 cpu 2021-12-31 14:06:35 +01:00
583e208c1e remark 2021-12-31 11:34:53 +01:00
9b91c427a1 add porting guide
sizeof(pointer) is hardcoded as 2 now
2021-12-31 00:16:23 +01:00
196c5e9c24 v39->r39 2021-12-30 19:05:56 +01:00
d8f7feb672 cleanup code style 2021-12-30 18:47:38 +01:00
7c889f17b9 c128 fixes 2021-12-30 18:33:26 +01:00
c15a75556d Merge branch 'master' into c128target
# Conflicts:
#	compiler/src/prog8/CompilerMain.kt
#	examples/test.p8
2021-12-30 18:22:05 +01:00
5275c2e35f todo porting guide 2021-12-30 18:20:09 +01:00
5267e06969 added -asmlist cli option to produce assembler listing output 2021-12-30 14:42:09 +01:00
b62183adcb slightly optimized binexpr evaluation for ==/!= in some cases 2021-12-30 02:00:36 +01:00
5d2dec1803 added missing codegen for augmented ==/!= 2021-12-30 01:34:10 +01:00
4a98dab948 fix compiler warnings 2021-12-30 00:58:33 +01:00
9f8c70b326 fix warning about testing multiple values 2021-12-30 00:49:36 +01:00
a65404e63a doc 2021-12-29 18:24:05 +01:00
05a1ddad05 Merge branch 'master' into c128target
# Conflicts:
#	examples/test.p8
2021-12-29 18:14:24 +01:00
4be3d63c0e slight optimization of if-in 2021-12-29 18:13:43 +01:00
de6ce4a46e add "X in [1,2,3]" expression (efficient containment check) 2021-12-29 17:26:00 +01:00
7a9e5afb93 fix: for loop over array literal no longer crashes the compiler 2021-12-28 17:51:38 +01:00
b2876b0a03 add a suggestion to use when statement if it seems appropriate 2021-12-28 16:38:12 +01:00
b66f66fe6a fix renames 2021-12-28 14:32:27 +01:00
30f04962d4 Merge branch 'master' into c128target
# Conflicts:
#	codeGeneration/src/prog8/codegen/target/C128Target.kt
#	codeGeneration/src/prog8/codegen/target/c128/C128MachineDefinition.kt
#	codeGeneration/src/prog8/codegen/target/c128/C128Zeropage.kt
#	compiler/src/prog8/CompilerMain.kt
#	compiler/src/prog8/compiler/Compiler.kt
2021-12-28 14:30:11 +01:00
0feeb88024 codegen package rename 2 2021-12-28 14:23:36 +01:00
b799f2e05b codegen package rename 2021-12-28 14:18:04 +01:00
56d21de001 Merge branch 'master' into c128target
# Conflicts:
#	examples/test.p8
2021-12-28 13:57:27 +01:00
7b54aa0c7d more consistent naming of the statement classes 2021-12-28 13:56:47 +01:00
6e11b8ada1 GoSub no longer inherits from Jump node, fixes subtle ast/codegen bugs related to jsrs 2021-12-28 01:55:13 +01:00
98d25fc4e9 fix some unneeded open classes 2021-12-28 01:29:08 +01:00
79405f47f6 fix if-gosub 2021-12-28 01:24:31 +01:00
1c7c4fc3b0 optimized if-goto codegeneration 2021-12-28 00:42:00 +01:00
97e84d0977 tweak if statement handling 2021-12-27 15:04:25 +01:00
9906b58818 tweak while desugaring, moved postfixexpr optimizations to VariousCleanups regardless of optimizer setting because asmgen requires these for conditional expressions 2021-12-27 12:41:26 +01:00
371f084884 comment 2021-12-27 02:17:04 +01:00
1c10839c14 moved peek/poke desugaring to other walker 2021-12-27 02:08:47 +01:00
c55fdd9834 removed special code generation for while and util expression (replaced by jumps)
also added exhaustive parent node checker in validation step
2021-12-27 02:04:28 +01:00
67b0890a6e remove unneeded var inits when an assignment is already present 2021-12-25 23:31:25 +01:00
4da4f96669 lower code: break -> goto after (simplifies codegen) 2021-12-25 22:30:38 +01:00
60a64c2558 test program for floating point issues on c128 2021-12-25 14:36:10 +01:00
d4153da8b9 setup float routine addresses for c128 2021-12-25 02:34:52 +01:00
fc33ab8905 shuffled some system functions back to c64 block to remain compatible with existing code, added missing float and graphics library stubs 2021-12-24 00:08:32 +01:00
a123c64f59 Merge branch 'master' into c128target 2021-12-23 23:51:42 +01:00
a090fe3834 no compiler optimizer crash on certain missing symbol 2021-12-23 23:51:21 +01:00
8fa84de28e fix c128 clearscreen and bdmusic sound issue 2021-12-22 22:59:36 +01:00
3e3da38de1 correctly disable charset switching 2021-12-22 21:47:41 +01:00
a3be8ccc87 Merge branch 'master' into c128target 2021-12-22 21:25:49 +01:00
cdfef30c22 fixed docs on rsave()/rrestore() builtin functions 2021-12-22 21:24:36 +01:00
cabf1e82e8 some shadow registers added to make uppercase()/lowercase() work 2021-12-22 21:20:34 +01:00
608dc5e284 readme 2021-12-22 00:36:06 +01:00
836d40072f c128 evalstack corrections 2021-12-22 00:07:05 +01:00
431401d90e c128 corrections 2021-12-21 23:37:15 +01:00
6da83e2bd7 first steps to add C128 compiler target 2021-12-21 19:08:33 +01:00
7bccfc0006 proper position in recursion warning 2021-12-17 21:04:41 +01:00
e051e09c1d trim down number of warnings a bit 2021-12-17 20:21:14 +01:00
1462c57d0c no need for intermediary returnvalue var for prefix expressions 2021-12-16 21:00:38 +01:00
77c2b2b326 fix position of @shared in array var declarations so that the order is now type[] @shared 2021-12-16 20:36:05 +01:00
3cf9b9d9a5 code size optimization: subroutine calls with 2 byte arg will pass it via A/Y registers instead of separate param assignments at every call site 2021-12-16 01:48:22 +01:00
629117e594 code size optimization: subroutine calls with 1 int arg will pass it via register instead of separate param assignment at every call site 2021-12-16 00:56:59 +01:00
08f87c321f fixed capitalization of operator sets to be consistent with other sets names 2021-12-15 23:43:14 +01:00
1ff13723fe implicit int to float conversion is now an error if floats are not enabled. 2021-12-15 01:52:28 +01:00
510bda1b28 fix compiler crash when using floats in a comparison expression 2021-12-15 01:24:25 +01:00
890327b381 the returnvalue of the diskio.load() function family now is just the last load address+1 (like kernal's LOAD routine).
This fixes the inconsistent attempt to calculate a size, just let the caller do this if required.
Added a small helper function in cx16diskio to do this for loads that span multiple banks.
2021-12-14 23:54:42 +01:00
b21f7411dd fix compiler crash when trying to concatenate string var and string literal. 2021-12-14 23:07:46 +01:00
5df623bd2e doc 2021-12-14 22:40:03 +01:00
1e9d249f71 fixed output of float values in cmp instructions 2021-12-13 00:17:59 +01:00
a7b5949e6a fix compiler crash when using a gosub/subroutinecall in a branch statement 2021-12-11 15:11:16 +01:00
02010170ce fix compiler crash when attempting to call a non-function 2021-12-11 13:20:13 +01:00
35998142fe version 7.5 2021-12-10 20:18:17 +01:00
75ea453bf4 fix asm code optimization problem caused in previous release where asm file is not read back in separate lines anymore 2021-12-10 15:37:53 +01:00
33061aaa0d fix: allow scoped variables such as cx16.rX as loop variable in forloops 2021-12-10 14:59:04 +01:00
e342311bef fix wrong code for inplace modification of a pointervariable's memory value 2021-12-10 14:48:53 +01:00
3d743a1ba1 added more constfolding 2021-12-09 23:32:48 +01:00
abca618008 added more constfolding 2021-12-09 23:12:12 +01:00
0d2c3901a3 added more constfolding 2021-12-09 22:12:31 +01:00
d901a1531f added missing vectors to syslib 2021-12-09 21:38:00 +01:00
d8d56b195f comments 2021-12-09 21:13:13 +01:00
a52699717c Merge remote-tracking branch 'origin/master'
# Conflicts:
#	compiler/src/prog8/compiler/astprocessing/AstChecker.kt
#	compiler/test/TestSubroutines.kt
#	examples/test.p8
2021-12-07 23:29:30 +01:00
98315de723 allow using ubyte[] as subroutine parameter type (because it is equivalent to uword pointer var) 2021-12-07 23:28:45 +01:00
68d2f7d4c0 allow using ubyte[] as subroutine parameter type (because it is equivalent to uword pointer var) 2021-12-07 23:21:49 +01:00
c812b5ee09 elaborate pointervar indexing a bit more in the docs 2021-12-07 22:25:14 +01:00
dcf487bdc1 fix: correctly insert return statement if needed to prevent 'fall through' into following subroutine
this wasn't working correctly anymore when the last statement before the subroutine was a jump/goto
2021-12-07 21:34:50 +01:00
547b1d3720 comment corrections 2021-12-06 23:33:18 +01:00
84f75f4156 tweaked some more .getOrElse 2021-12-06 21:22:00 +01:00
ff69da3fa2 error when 'else' choice in when statemetn isn't the last one, also generate slightly better code for when statements 2021-12-05 21:54:46 +01:00
edffe92a24 astchecker is smarter in detecting rts in inline assembly 2021-12-05 21:28:31 +01:00
b6fe40ada4 fix: cx16.r0 now properly treated as zeropage var on cx16 so @(cx16.r0) won't copy it to temp var anymore 2021-12-05 21:21:41 +01:00
837804b231 test for string x and u escape sequences 2021-12-05 18:39:34 +01:00
81deed143b fix grammar problem: \x and \u escape sequences didn't work in character literals. 2021-12-05 18:11:40 +01:00
900cdd3fa1 added cx16diskio with load() and load_raw() that are HIMEM bank-aware 2021-12-05 02:20:48 +01:00
0018dc6ce7 refactor machinedefinition 2021-12-04 19:07:19 +01:00
c92f914081 gradle build settings tweak to avoid jdk version conflict 2021-12-04 18:36:47 +01:00
0498444ef2 moved all unit tests into single project to avoid dependency issues 2021-12-04 18:20:22 +01:00
ce3c34e458 tweak in error output for file links, corrected column number off-by-one 2021-12-04 16:52:03 +01:00
20401b99d8 added cx16.getrambank() / getrombank() to retrieve the current ram and rom bank numbers. 2021-12-04 15:27:54 +01:00
397f98513b optimize loading A from pointervar 2021-12-04 05:36:48 +01:00
e545ea9504 fix and optimize storing A into pointervar 2021-12-04 04:43:58 +01:00
b867d8f731 cleanups 2021-12-04 01:03:51 +01:00
9a68864b67 version 7.5-dev 2021-12-04 00:18:44 +01:00
72d7178762 added diskio.load_raw() to load headerless files 2021-12-04 00:07:21 +01:00
fbcd9a0c1d reduce number of similar errors for type problem in assignment 2021-12-02 17:44:52 +01:00
c3144a20db spacing 2021-12-02 00:10:06 +01:00
5b56e0462d also deal with zero args 2021-12-01 22:26:36 +01:00
b7fffbb6df release 7.4.1 - oops, funcion call arg count validation was broken 2021-12-01 21:44:03 +01:00
342 changed files with 28245 additions and 10064 deletions

10
.idea/codeStyles/Project.xml generated Normal file
View File

@ -0,0 +1,10 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

4
.idea/compiler.xml generated
View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="BUILD_PROCESS_HEAP_SIZE" value="1200" />
<option name="BUILD_PROCESS_HEAP_SIZE" value="3000" />
<bytecodeTargetLevel target="11" />
</component>
</project>
</project>

View File

@ -7,6 +7,7 @@
<language isEnabled="false" name="Groovy" />
</Languages>
</inspection_tool>
<inspection_tool class="IncompleteDestructuring" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />

View File

@ -1,15 +1,15 @@
<component name="libraryTable">
<library name="antlr.antlr4" type="repository">
<properties maven-id="org.antlr:antlr4:4.9.2">
<properties maven-id="org.antlr:antlr4:4.10.1">
<exclude>
<dependency maven-id="com.ibm.icu:icu4j" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.9.2/antlr4-4.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.9.2/antlr4-runtime-4.9.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.2/antlr-runtime-3.5.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3/ST4-4.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.10.1/antlr4-4.10.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.10.1/antlr4-runtime-4.10.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.3/ST4-4.3.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar!/" />
</CLASSES>

View File

@ -1,10 +0,0 @@
<component name="libraryTable">
<library name="hamcrest" type="repository">
<properties maven-id="org.hamcrest:hamcrest:2.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest/2.2/hamcrest-2.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,20 +1,21 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:4.6.3" />
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.1.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.1.0/kotest-assertions-core-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.10/kotlin-stdlib-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.1.0/kotest-assertions-shared-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.0/kotlinx-coroutines-jdk8-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.0/kotlinx-coroutines-core-jvm-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.1.0/kotest-common-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.1.0/kotest-assertions-api-jvm-5.1.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

View File

@ -1,22 +1,22 @@
<component name="libraryTable">
<library name="io.kotest.property.jvm" type="repository">
<properties maven-id="io.kotest:kotest-property-jvm:4.6.3" />
<properties maven-id="io.kotest:kotest-property-jvm:5.1.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/4.6.3/kotest-property-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-property-jvm/5.1.0/kotest-property-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.1.0/kotest-common-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.1.0/kotest-assertions-shared-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.1.0/kotest-assertions-api-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.0/kotlinx-coroutines-jdk8-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.10/kotlin-stdlib-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/mifmif/generex/1.0.2/generex-1.0.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/dk/brics/automaton/automaton/1.11-8/automaton-1.11-8.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.0/kotlinx-coroutines-core-jvm-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/curious-odd-man/rgxgen/1.3/rgxgen-1.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

View File

@ -1,54 +1,51 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:4.6.3" />
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.1.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/4.6.3/kotest-runner-junit5-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/4.6.3/kotest-framework-api-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/4.6.3/kotest-assertions-shared-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.9/java-diff-utils-4.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/4.6.3/kotest-common-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/4.6.3/kotest-framework-engine-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.105/classgraph-4.8.105.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.1.0/kotest-runner-junit5-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.1.0/kotest-framework-api-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.6.0/kotlinx-coroutines-test-jvm-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.1.0/kotest-assertions-shared-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.11/java-diff-utils-4.11.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.1.0/kotest-common-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.1.0/kotest-framework-engine-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.138/classgraph-4.8.138.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-util/1.5.0/kotlin-script-util-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/intellij/deps/trove4j/1.0.20181211/trove4j-1.0.20181211.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-daemon-client/1.5.0/kotlin-daemon-client-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core/1.3.8/kotlinx-coroutines-core-1.3.8.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-jvm/1.5.0/kotlin-scripting-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-scripting-common/1.5.0/kotlin-scripting-common-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.6.0/kotlinx-coroutines-debug-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/4.6.3/kotest-framework-discovery-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/4.6.3/kotest-assertions-core-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.5.0/kotlinx-coroutines-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/4.6.3/kotest-assertions-api-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/4.6.3/kotest-extensions-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.6/commons-io-2.6.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.9.3/mockk-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.9.3/mockk-common-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.9.3/mockk-dsl-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.9.3/mockk-dsl-jvm-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.9.3/mockk-agent-jvm-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api/1.9.3/mockk-agent-api-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-common/1.9.3/mockk-agent-common-1.9.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.0.1/objenesis-3.0.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.9.10/byte-buddy-1.9.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.9.10/byte-buddy-agent-1.9.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/4.6.3/kotest-framework-concurrency-jvm-4.6.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.5.0/kotlinx-coroutines-core-jvm-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.0/kotlin-stdlib-common-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.6.2/junit-platform-engine-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.1.0/kotest-framework-discovery-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.1.0/kotest-assertions-core-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.6.0/kotlinx-coroutines-jdk8-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.1.0/kotest-assertions-api-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.1.0/kotest-extensions-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.11.0/commons-io-2.11.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk/1.12.2/mockk-1.12.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl-jvm/1.12.2/mockk-dsl-jvm-1.12.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-dsl/1.12.2/mockk-dsl-1.12.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-common/1.12.2/mockk-common-1.12.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-jvm/1.12.2/mockk-agent-jvm-1.12.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-api/1.12.2/mockk-agent-api-1.12.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/mockk/mockk-agent-common/1.12.2/mockk-agent-common-1.12.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/objenesis/objenesis/3.1/objenesis-3.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.12.5/byte-buddy-1.12.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.12.5/byte-buddy-agent-1.12.5.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.1.0/kotest-framework-concurrency-jvm-5.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.6.0/kotlinx-coroutines-core-jvm-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.0/kotlin-stdlib-common-1.6.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.6.2/junit-platform-commons-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.6.2/junit-platform-suite-api-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.6.2/junit-platform-launcher-1.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.6.2/junit-jupiter-api-5.6.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.0/kotlin-stdlib-jdk8-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.0/kotlin-stdlib-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.7.2/junit-platform-suite-api-1.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.7.2/junit-platform-launcher-1.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.10/kotlin-stdlib-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.0/kotlin-stdlib-jdk7-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-script-runtime/1.5.0/kotlin-script-runtime-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.5.0/kotlin-reflect-1.5.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.6.10/kotlin-reflect-1.6.10.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

View File

@ -1,8 +1,8 @@
<component name="libraryTable">
<library name="jetbrains.kotlinx.cli.jvm" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.3" />
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-cli-jvm:0.3.4" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.3/kotlinx-cli-jvm-0.3.3.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-cli-jvm/0.3.4/kotlinx-cli-jvm-0.3.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

View File

@ -1,17 +0,0 @@
<component name="libraryTable">
<library name="junit.jupiter" type="repository">
<properties maven-id="org.junit.jupiter:junit-jupiter:5.7.2" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter/5.7.2/junit-jupiter-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.7.2/junit-jupiter-api-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/apiguardian/apiguardian-api/1.1.0/apiguardian-api-1.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.2.0/opentest4j-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.7.2/junit-platform-commons-1.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-params/5.7.2/junit-jupiter-params-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-engine/5.7.2/junit-jupiter-engine-5.7.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.7.2/junit-platform-engine-1.7.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,13 +1,13 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.12" />
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.12/kotlin-result-jvm-1.1.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.5.10/kotlin-stdlib-jdk8-1.5.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.5.10/kotlin-stdlib-1.5.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.14/kotlin-result-jvm-1.1.14.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.6.10/kotlin-stdlib-jdk8-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.6.10/kotlin-stdlib-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.5.10/kotlin-stdlib-jdk7-1.5.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.5.10/kotlin-stdlib-common-1.5.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.6.10/kotlin-stdlib-jdk7-1.6.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.6.10/kotlin-stdlib-common-1.6.10.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

View File

@ -1,9 +1,9 @@
<component name="libraryTable">
<library name="slf4j.simple" type="repository">
<properties maven-id="org.slf4j:slf4j-simple:1.7.30" />
<properties maven-id="org.slf4j:slf4j-simple:1.7.36" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.30/slf4j-simple-1.7.30.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-simple/1.7.36/slf4j-simple-1.7.36.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

2
.idea/misc.xml generated
View File

@ -19,7 +19,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated
View File

@ -2,16 +2,20 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/codeGeneration/codeGeneration.iml" filepath="$PROJECT_DIR$/codeGeneration/codeGeneration.iml" />
<module fileurl="file://$PROJECT_DIR$/codeAst/codeAst.iml" filepath="$PROJECT_DIR$/codeAst/codeAst.iml" />
<module fileurl="file://$PROJECT_DIR$/codeCore/codeCore.iml" filepath="$PROJECT_DIR$/codeCore/codeCore.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" filepath="$PROJECT_DIR$/codeGenExperimental/codeGenExperimental.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" filepath="$PROJECT_DIR$/codeGenVirtual/codeGenVirtual.iml" />
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" filepath="$PROJECT_DIR$/compilerInterfaces/compilerInterfaces.iml" />
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.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" />
<module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
</modules>
</component>
</project>

2
.idea/vcs.xml generated
View File

@ -3,4 +3,4 @@
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
</project>

View File

@ -12,6 +12,9 @@
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
#### Steps to take, in conceptual (!) order:
(note: all these steps have been implemented, rejected or otherwise solved now.)
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
- from the local file system (use case: user programs)
- from resources (prog8lib)

View File

@ -1,6 +1,9 @@
This sofware license is for Prog8 the compiler + associated libraries.
The software generated by running the compiler is excluded from this.
Any and all outputs generated by the compiler (intermediary
source code and compiled binary programs) are excluded from that
and you can do with them whatever you want.

View File

@ -37,7 +37,8 @@ What does Prog8 provide?
- small program boilerplate/compilersupport overhead
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches
- 'when' statement to provide a concise jump table alternative to if/elseif chains
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``in`` expression for concise and efficient multi-value/containment check
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
@ -52,11 +53,12 @@ What does Prog8 provide?
- 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
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
*Multiple supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
- "c64": Commodore-64 (6510 CPU = almost a 6502)
- "c64": Commodore-64 (6502 like CPU)
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)

View File

@ -11,10 +11,21 @@ java {
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':compilerAst')
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}
sourceSets {

View File

@ -4,12 +4,11 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<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="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="module" module-name="codeCore" />
</component>
</module>

View File

@ -0,0 +1,217 @@
package prog8.code
import prog8.code.core.*
/**
* Tree structure containing all symbol definitions in the program
* (blocks, subroutines, variables (all types) and labels).
*/
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
fun print() = printIndented(0)
override fun printProperties() { }
/**
* The table as a flat mapping of scoped names to the StNode.
* This gives the fastest lookup possible (no need to traverse tree nodes)
*/
val flat: Map<List<String>, StNode> by lazy {
val result = mutableMapOf<List<String>, StNode>()
fun flatten(node: StNode) {
result[node.scopedName] = node
node.children.values.forEach { flatten(it) }
}
children.values.forEach { flatten(it) }
result
}
val allVariables: Collection<StStaticVariable> by lazy {
val vars = mutableListOf<StStaticVariable>()
fun collect(node: StNode) {
for(child in node.children) {
if(child.value.type== StNodeType.STATICVAR)
vars.add(child.value as StStaticVariable)
else
collect(child.value)
}
}
collect(this)
vars
}
override fun lookup(scopedName: List<String>) = flat[scopedName]
}
enum class StNodeType {
GLOBAL,
// MODULE, // not used with current scoping rules
BLOCK,
SUBROUTINE,
ROMSUB,
LABEL,
STATICVAR,
MEMVAR,
CONSTANT,
BUILTINFUNC
}
open class StNode(val name: String,
val type: StNodeType,
val position: Position,
val children: MutableMap<String, StNode> = mutableMapOf()
) {
lateinit var parent: StNode
val scopedName: List<String> by lazy {
if(type== StNodeType.GLOBAL)
emptyList()
else
parent.scopedName + name
}
fun lookup(name: String) =
lookupUnqualified(name)
open fun lookup(scopedName: List<String>) =
if(scopedName.size>1) lookupQualified(scopedName) else lookupUnqualified(scopedName[0])
fun lookupOrElse(name: String, default: () -> StNode) =
lookupUnqualified(name) ?: default()
fun lookupOrElse(scopedName: List<String>, default: () -> StNode) =
lookup(scopedName) ?: default()
private fun lookupQualified(scopedName: List<String>): StNode? {
// a scoped name refers to a name in another namespace, and always stars from the root.
var node = this
while(node.type!= StNodeType.GLOBAL)
node = node.parent
for(name in scopedName) {
if(name in node.children)
node = node.children.getValue(name)
else
return null
}
return node
}
private fun lookupUnqualified(name: String): StNode? {
// first consider the builtin functions
var globalscope = this
while(globalscope.type!= StNodeType.GLOBAL)
globalscope = globalscope.parent
val globalNode = globalscope.children[name]
if(globalNode!=null && globalNode.type== StNodeType.BUILTINFUNC)
return globalNode
// search for the unqualified name in the current scope or its parent scopes
var scope=this
while(true) {
val node = scope.children[name]
if(node!=null)
return node
if(scope.type== StNodeType.GLOBAL)
return null
else
scope = scope.parent
}
}
fun printIndented(indent: Int) {
print(" ".repeat(indent))
when(type) {
StNodeType.GLOBAL -> print("SYMBOL-TABLE:")
StNodeType.BLOCK -> print("(B) ")
StNodeType.SUBROUTINE -> print("(S) ")
StNodeType.LABEL -> print("(L) ")
StNodeType.STATICVAR -> print("(V) ")
StNodeType.MEMVAR -> print("(M) ")
StNodeType.CONSTANT -> print("(C) ")
StNodeType.BUILTINFUNC -> print("(F) ")
StNodeType.ROMSUB -> print("(R) ")
}
printProperties()
println()
children.forEach { (_, node) -> node.printIndented(indent+1) }
}
open fun printProperties() {
print("$name ")
}
fun add(child: StNode) {
children[child.name] = child
child.parent = this
}
}
class StStaticVariable(name: String,
val dt: DataType,
val initialNumericValue: Double?,
val initialStringValue: StString?,
val initialArrayValue: StArray?,
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
val zpwish: ZeropageWish,
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
init {
if(length!=null) {
require(initialNumericValue == null)
if(initialArrayValue!=null)
require(length == initialArrayValue.size)
}
if(initialNumericValue!=null)
require(dt in NumericDatatypes)
if(initialArrayValue!=null)
require(dt in ArrayDatatypes)
if(initialStringValue!=null) {
require(dt == DataType.STR)
require(length == initialStringValue.first.length+1)
}
}
override fun printProperties() {
print("$name dt=$dt zpw=$zpwish")
}
}
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
StNode(name, StNodeType.CONSTANT, position) {
override fun printProperties() {
print("$name dt=$dt value=$value")
}
}
class StMemVar(name: String, val dt: DataType, val address: UInt, position: Position) :
StNode(name, StNodeType.MEMVAR, position) {
override fun printProperties() {
print("$name dt=$dt address=${address.toHex()}")
}
}
class StSub(name: String, val parameters: List<StSubroutineParameter>, position: Position) :
StNode(name, StNodeType.SUBROUTINE, position) {
override fun printProperties() {
print(name)
}
}
class StRomSub(name: String, val address: UInt, parameters: List<StSubroutineParameter>, position: Position) :
StNode(name, StNodeType.ROMSUB, position) {
override fun printProperties() {
print("$name address=${address.toHex()}")
}
}
class StSubroutineParameter(val name: String, val type: DataType)
class StArrayElement(val number: Double?, val addressOf: List<String>?)
typealias StString = Pair<String, Encoding>
typealias StArray = List<StArrayElement>

View File

@ -0,0 +1,144 @@
package prog8.code.ast
import prog8.code.core.*
import java.nio.file.Path
// New (work-in-progress) simplified AST for the code generator.
sealed class PtNode(val position: Position) {
val children = mutableListOf<PtNode>()
lateinit var parent: PtNode
fun printIndented(indent: Int) {
print(" ".repeat(indent))
print("${this.javaClass.simpleName} ")
printProperties()
println()
children.forEach { it.printIndented(indent+1) }
}
abstract fun printProperties()
fun add(child: PtNode) {
children.add(child)
child.parent = this
}
fun add(index: Int, child: PtNode) {
children.add(index, child)
child.parent = this
}
fun definingBlock() = findParentNode<PtBlock>(this)
fun definingSub() = findParentNode<PtSub>(this)
fun definingAsmSub() = findParentNode<PtAsmSub>(this)
}
class PtNodeGroup : PtNode(Position.DUMMY) {
override fun printProperties() {}
}
abstract class PtNamedNode(val name: String, position: Position): PtNode(position) {
val scopedName: List<String> by lazy {
var namedParent: PtNode = this.parent
if(namedParent is PtProgram)
listOf(name)
else {
while (namedParent !is PtNamedNode)
namedParent = namedParent.parent
namedParent.scopedName + name
}
}
}
class PtProgram(
val name: String,
val memsizer: IMemSizer,
val encoding: IStringEncoding
) : PtNode(Position.DUMMY) {
fun print() = printIndented(0)
override fun printProperties() {
print("'$name'")
}
// fun allModuleDirectives(): Sequence<PtDirective> =
// children.asSequence().flatMap { it.children }.filterIsInstance<PtDirective>().distinct()
fun allBlocks(): Sequence<PtBlock> =
children.asSequence().filterIsInstance<PtBlock>()
fun entrypoint(): PtSub? =
allBlocks().firstOrNull { it.name == "main" }?.children?.firstOrNull { it is PtSub && it.name == "start" } as PtSub?
}
class PtBlock(name: String,
val address: UInt?,
val library: Boolean,
val forceOutput: Boolean,
val alignment: BlockAlignment,
position: Position
) : PtNamedNode(name, position) {
override fun printProperties() {
print("$name addr=$address library=$library forceOutput=$forceOutput alignment=$alignment")
}
enum class BlockAlignment {
NONE,
WORD,
PAGE
}
}
class PtInlineAssembly(val assembly: String, position: Position) : PtNode(position) {
override fun printProperties() {}
}
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
override fun printProperties() {
print(name)
}
}
class PtBreakpoint(position: Position): PtNode(position) {
override fun printProperties() {}
}
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position) {
override fun printProperties() {
print("filename=$file offset=$offset length=$length")
}
}
class PtNop(position: Position): PtNode(position) {
override fun printProperties() {}
}
class PtScopeVarsDecls(position: Position): PtNode(position) {
override fun printProperties() {}
}
// find the parent node of a specific type or interface
// (useful to figure out in what namespace/block something is defined, etc.)
inline fun <reified T> findParentNode(node: PtNode): T? {
var candidate = node.parent
while(candidate !is T && candidate !is PtProgram)
candidate = candidate.parent
return if(candidate is PtProgram)
null
else
candidate as T
}

View File

@ -0,0 +1,216 @@
package prog8.code.ast
import prog8.code.core.DataType
import prog8.code.core.Encoding
import prog8.code.core.Position
import java.util.*
import kotlin.math.round
sealed class PtExpression(val type: DataType, position: Position) : PtNode(position) {
override fun printProperties() {
print(type)
}
infix fun isSameAs(other: PtExpression): Boolean {
return when(this) {
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
is PtIdentifier -> other is PtIdentifier && other.type==type && other.targetName==targetName
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
is PtNumber -> other is PtNumber && other.type==type && other.number==number
is PtPrefix -> other is PtPrefix && other.type==type && other.operator==operator && other.value isSameAs value
is PtRange -> other is PtRange && other.type==type && other.from==from && other.to==to && other.step==step
is PtTypeCast -> other is PtTypeCast && other.type==type && other.value isSameAs value
else -> false
}
}
}
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
val identifier: PtIdentifier
get() = children.single() as PtIdentifier
}
class PtArrayIndexer(type: DataType, position: Position): PtExpression(type, position) {
val variable: PtIdentifier
get() = children[0] as PtIdentifier
val index: PtExpression
get() = children[1] as PtExpression
}
class PtArray(type: DataType, position: Position): PtExpression(type, position) {
override fun hashCode(): Int = Objects.hash(children, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is PtArray)
return false
return type==other.type && children == other.children
}
}
class PtBuiltinFunctionCall(val name: String,
val void: Boolean,
val hasNoSideEffects: Boolean,
type: DataType,
position: Position) : PtExpression(type, position) {
init {
if(!void)
require(type!=DataType.UNDEFINED)
}
val args: List<PtExpression>
get() = children.map { it as PtExpression }
override fun printProperties() {
print("$name void=$void noSideFx=$hasNoSideEffects")
}
}
class PtBinaryExpression(val operator: String, type: DataType, position: Position): PtExpression(type, position) {
val left: PtExpression
get() = children[0] as PtExpression
val right: PtExpression
get() = children[1] as PtExpression
override fun printProperties() {
print("$operator -> $type")
}
}
class PtContainmentCheck(position: Position): PtExpression(DataType.UBYTE, position) {
val element: PtExpression
get() = children[0] as PtExpression
val iterable: PtIdentifier
get() = children[1] as PtIdentifier
}
class PtFunctionCall(val functionName: List<String>,
val void: Boolean,
type: DataType,
position: Position) : PtExpression(type, position) {
init {
if(!void)
require(type!=DataType.UNDEFINED)
}
val args: List<PtExpression>
get() = children.map { it as PtExpression }
override fun printProperties() {
print("${functionName.joinToString(".")} void=$void")
}
}
class PtIdentifier(val ref: List<String>, val targetName: List<String>, type: DataType, position: Position) : PtExpression(type, position) {
override fun printProperties() {
print("$ref --> $targetName $type")
}
}
class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) {
val address: PtExpression
get() = children.single() as PtExpression
override fun printProperties() {}
}
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
init {
if(type!=DataType.FLOAT) {
val rounded = round(number)
if (rounded != number)
throw IllegalArgumentException("refused rounding of float to avoid loss of precision")
}
}
override fun printProperties() {
print("$number ($type)")
}
override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean {
if(other==null || other !is PtNumber)
return false
return number==other.number
}
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
}
class PtPipe(type: DataType, val void: Boolean, position: Position) : PtExpression(type, position) {
init {
if(!void)
require(type!=DataType.UNDEFINED)
}
val segments: List<PtExpression>
get() = children.map { it as PtExpression }
override fun printProperties() {}
}
class PtPrefix(val operator: String, type: DataType, position: Position): PtExpression(type, position) {
val value: PtExpression
get() = children.single() as PtExpression
override fun printProperties() {
print(operator)
}
}
class PtRange(type: DataType, position: Position) : PtExpression(type, position) {
val from: PtExpression
get() = children[0] as PtExpression
val to: PtExpression
get() = children[1] as PtExpression
val step: PtNumber
get() = children[2] as PtNumber
override fun printProperties() {}
}
class PtString(val value: String, val encoding: Encoding, position: Position) : PtExpression(DataType.STR, position) {
override fun printProperties() {
print("$encoding:\"$value\"")
}
override fun hashCode(): Int = Objects.hash(value, encoding)
override fun equals(other: Any?): Boolean {
if(other==null || other !is PtString)
return false
return value==other.value && encoding == other.encoding
}
}
class PtTypeCast(type: DataType, position: Position) : PtExpression(type, position) {
val value: PtExpression
get() = children.single() as PtExpression
}
// special node that isn't created from compiling user code, but used internally
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position) {
override fun printProperties() {
print("reg=$register $type")
}
}
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null
fun constIntValue(expr: PtExpression): Int? = if(expr is PtNumber) expr.number.toInt() else null

View File

@ -0,0 +1,219 @@
package prog8.code.ast
import prog8.code.core.*
class PtAsmSub(
name: String,
val address: UInt?,
val clobbers: Set<CpuRegister>,
val parameters: List<Pair<PtSubroutineParameter, RegisterOrStatusflag>>,
val retvalRegisters: List<RegisterOrStatusflag>,
val inline: Boolean,
position: Position
) : PtNamedNode(name, position) {
override fun printProperties() {
print("$name inline=$inline")
}
}
class PtSub(
name: String,
val parameters: List<PtSubroutineParameter>,
val returntype: DataType?,
val inline: Boolean,
position: Position
) : PtNamedNode(name, position) {
override fun printProperties() {
print(name)
}
}
class PtSubroutineParameter(val name: String, val type: DataType, position: Position): PtNode(position) {
override fun printProperties() {
print("$type $name")
}
}
class PtAssignment(position: Position) : PtNode(position) {
val target: PtAssignTarget
get() = children[0] as PtAssignTarget
val value: PtExpression
get() = children[1] as PtExpression
override fun printProperties() { }
val isInplaceAssign: Boolean by lazy {
val target = target.children.single() as PtExpression
when(val source = value) {
is PtArrayIndexer -> {
if(target is PtArrayIndexer && source.type==target.type) {
if(target.variable isSameAs source.variable) {
target.index isSameAs source.index
}
}
false
}
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.targetName==source.targetName
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
is PtAddressOf -> target is PtAddressOf && target.identifier isSameAs source.identifier
is PtPrefix -> {
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
||
(target is PtIdentifier && (source.value as? PtIdentifier)?.targetName==target.targetName)
}
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
is PtBinaryExpression ->
target isSameAs source.left
else -> false
}
}
}
class PtAssignTarget(position: Position) : PtNode(position) {
val identifier: PtIdentifier?
get() = children.single() as? PtIdentifier
val array: PtArrayIndexer?
get() = children.single() as? PtArrayIndexer
val memory: PtMemoryByte?
get() = children.single() as? PtMemoryByte
val type: DataType
get() {
return when(val tgt = children.single()) {
is PtIdentifier -> tgt.type
is PtArrayIndexer -> tgt.type
is PtMemoryByte -> tgt.type
else -> throw AssemblyError("weird target $tgt")
}
}
override fun printProperties() {}
}
class PtConditionalBranch(val condition: BranchCondition, position: Position) : PtNode(position) {
val trueScope: PtNodeGroup
get() = children[0] as PtNodeGroup
val falseScope: PtNodeGroup
get() = children[1] as PtNodeGroup
override fun printProperties() {
print(condition)
}
}
class PtForLoop(position: Position) : PtNode(position) {
val variable: PtIdentifier
get() = children[0] as PtIdentifier
val iterable: PtExpression
get() = children[1] as PtExpression
val statements: PtNodeGroup
get() = children[2] as PtNodeGroup
override fun printProperties() {}
}
class PtIfElse(position: Position) : PtNode(position) {
val condition: PtBinaryExpression
get() = children[0] as PtBinaryExpression
val ifScope: PtNodeGroup
get() = children[1] as PtNodeGroup
val elseScope: PtNodeGroup
get() = children[2] as PtNodeGroup
override fun printProperties() {}
}
class PtJump(val identifier: PtIdentifier?,
val address: UInt?,
val generatedLabel: String?,
position: Position) : PtNode(position) {
override fun printProperties() {
identifier?.printProperties()
if(address!=null) print(address.toHex())
if(generatedLabel!=null) print(generatedLabel)
}
}
class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) {
val target: PtAssignTarget
get() = children.single() as PtAssignTarget
override fun printProperties() {
print(operator)
}
}
class PtRepeatLoop(position: Position) : PtNode(position) {
val count: PtExpression
get() = children[0] as PtExpression
val statements: PtNodeGroup
get() = children[1] as PtNodeGroup
override fun printProperties() {}
}
class PtReturn(position: Position) : PtNode(position) {
val hasValue = children.any()
val value: PtExpression?
get() {
return if(children.any())
children.single() as PtExpression
else
null
}
override fun printProperties() {}
}
class PtVariable(name: String, val type: DataType, var value: PtExpression?, var arraySize: UInt?, position: Position) : PtNamedNode(name, position) {
override fun printProperties() {
print("$type $name")
}
}
class PtConstant(name: String, val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position) {
override fun printProperties() {
print("$type $name = $value")
}
}
class PtMemMapped(name: String, val type: DataType, val address: UInt, position: Position) : PtNamedNode(name, position) {
override fun printProperties() {
print("&$type $name = ${address.toHex()}")
}
}
class PtWhen(position: Position) : PtNode(position) {
val value: PtExpression
get() = children[0] as PtExpression
val choices: PtNodeGroup
get() = children[1] as PtNodeGroup
override fun printProperties() {}
}
class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
val values: PtNodeGroup
get() = children[0] as PtNodeGroup
val statements: PtNodeGroup
get() = children[1] as PtNodeGroup
override fun printProperties() {}
}

43
codeCore/build.gradle Normal file
View File

@ -0,0 +1,43 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
// should have no dependencies to other modules
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
}
// note: there are no unit tests in this module!

14
codeCore/codeCore.iml Normal file
View File

@ -0,0 +1,14 @@
<?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$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>

View File

@ -0,0 +1,24 @@
package prog8.code.core
import java.nio.file.Path
import kotlin.io.path.Path
class CompilationOptions(val output: OutputType,
val launcher: CbmPrgLauncherType,
val zeropage: ZeropageType,
val zpReserved: List<UIntRange>,
val floats: Boolean,
val noSysInit: Boolean,
val compTarget: ICompilationTarget,
// these are set later, based on command line arguments or options in the source code:
var loadAddress: UInt,
var slowCodegenWarnings: Boolean = false,
var optimize: Boolean = false,
var optimizeFloatExpressions: Boolean = false,
var dontReinitGlobals: Boolean = false,
var asmQuiet: Boolean = false,
var asmListfile: Boolean = false,
var experimentalCodegen: Boolean = false,
var outputDir: Path = Path("")
)

View File

@ -0,0 +1,89 @@
package prog8.code.core
import kotlin.math.abs
fun Number.toHex(): String {
// 0..15 -> "0".."15"
// 16..255 -> "$10".."$ff"
// 256..65536 -> "$0100".."$ffff"
// negative values are prefixed with '-'.
val integer = this.toInt()
if(integer<0)
return '-' + abs(integer).toHex()
return when (integer) {
in 0 until 16 -> integer.toString()
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
else -> throw IllegalArgumentException("number too large for 16 bits $this")
}
}
fun UInt.toHex(): String {
// 0..15 -> "0".."15"
// 16..255 -> "$10".."$ff"
// 256..65536 -> "$0100".."$ffff"
return when (this) {
in 0u until 16u -> this.toString()
in 0u until 0x100u -> "$"+this.toString(16).padStart(2,'0')
in 0u until 0x10000u -> "$"+this.toString(16).padStart(4,'0')
else -> throw IllegalArgumentException("number too large for 16 bits $this")
}
}
fun Char.escape(): Char = this.toString().escape()[0]
fun String.escape(): String {
val es = this.map {
when(it) {
'\t' -> "\\t"
'\n' -> "\\n"
'\r' -> "\\r"
'"' -> "\\\""
in '\u8000'..'\u80ff' -> "\\x" + (it.code - 0x8000).toString(16).padStart(2, '0') // 'ugly' passthrough hack
in '\u0000'..'\u00ff' -> it.toString()
else -> "\\u" + it.code.toString(16).padStart(4, '0')
}
}
return es.joinToString("")
}
fun String.unescape(): String {
val result = mutableListOf<Char>()
val iter = this.iterator()
while(iter.hasNext()) {
val c = iter.nextChar()
if(c=='\\') {
val ec = iter.nextChar()
result.add(when(ec) {
'\\' -> '\\'
'n' -> '\n'
'r' -> '\r'
'"' -> '"'
'\'' -> '\''
'u' -> {
try {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
} catch (sb: StringIndexOutOfBoundsException) {
throw IllegalArgumentException("invalid \\u escape sequence")
} catch (nf: NumberFormatException) {
throw IllegalArgumentException("invalid \\u escape sequence")
}
}
'x' -> {
try {
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
(0x8000 + hex).toChar() // 'ugly' pass-through hack
} catch (sb: StringIndexOutOfBoundsException) {
throw IllegalArgumentException("invalid \\x escape sequence")
} catch (nf: NumberFormatException) {
throw IllegalArgumentException("invalid \\x escape sequence")
}
}
else -> throw IllegalArgumentException("invalid escape char in string: \\$ec")
})
} else {
result.add(c)
}
}
return result.joinToString("")
}

View File

@ -0,0 +1,182 @@
package prog8.code.core
enum class DataType {
UBYTE, // pass by value
BYTE, // pass by value
UWORD, // pass by value
WORD, // pass by value
FLOAT, // pass by value
STR, // pass by reference
ARRAY_UB, // pass by reference
ARRAY_B, // pass by reference
ARRAY_UW, // pass by reference
ARRAY_W, // pass by reference
ARRAY_F, // pass by reference
UNDEFINED;
/**
* is the type assignable to the given other type (perhaps via a typecast) without loss of precision?
*/
infix fun isAssignableTo(targetType: DataType) =
when(this) {
UBYTE -> targetType.oneOf(UBYTE, WORD, UWORD, FLOAT)
BYTE -> targetType.oneOf(BYTE, WORD, FLOAT)
UWORD -> targetType.oneOf(UWORD, FLOAT)
WORD -> targetType.oneOf(WORD, FLOAT)
FLOAT -> targetType == FLOAT
STR -> targetType.oneOf(STR, UWORD)
in ArrayDatatypes -> targetType == this
else -> false
}
fun oneOf(vararg types: DataType) = this in types
infix fun largerThan(other: DataType) =
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 == 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
}
}
enum class CpuRegister {
A,
X,
Y;
fun asRegisterOrPair(): RegisterOrPair = when(this) {
A -> RegisterOrPair.A
X -> RegisterOrPair.X
Y -> RegisterOrPair.Y
}
}
enum class RegisterOrPair {
A,
X,
Y,
AX,
AY,
XY,
FAC1,
FAC2,
// cx16 virtual registers:
R0, R1, R2, R3, R4, R5, R6, R7,
R8, R9, R10, R11, R12, R13, R14, R15;
companion object {
val names by lazy { values().map { it.toString()} }
}
fun asCpuRegister(): CpuRegister = when(this) {
A -> CpuRegister.A
X -> CpuRegister.X
Y -> CpuRegister.Y
else -> throw IllegalArgumentException("no cpu hardware register for $this")
}
} // only used in parameter and return value specs in asm subroutines
enum class Statusflag {
Pc,
Pz, // don't use
Pv,
Pn; // don't use
companion object {
val names by lazy { values().map { it.toString()} }
}
}
enum class BranchCondition {
CS,
CC,
EQ, // EQ == Z
Z,
NE, // NE == NZ
NZ,
MI, // MI == NEG
NEG,
PL, // PL == POS
POS,
VS,
VC,
}
val ByteDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = arrayOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = arrayOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val SignedDatatypes = arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
val IntegerArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W)
val ArrayDatatypes = arrayOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
val StringlyDatatypes = arrayOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
val IterableDatatypes = arrayOf(
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F
)
val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes
val ArrayToElementTypes = mapOf(
DataType.STR 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 ElementToArrayTypes = 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
)
val Cx16VirtualRegisters = arrayOf(
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
RegisterOrPair.R8, RegisterOrPair.R9, RegisterOrPair.R10, RegisterOrPair.R11,
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
enum class OutputType {
RAW,
PRG,
XEX
}
enum class CbmPrgLauncherType {
BASIC,
NONE
}
enum class ZeropageType {
BASICSAFE,
FLOATSAFE,
KERNALSAFE,
FULL,
DONTUSE
}
enum class ZeropageWish {
REQUIRE_ZEROPAGE,
PREFER_ZEROPAGE,
DONTCARE,
NOT_IN_ZEROPAGE
}

View File

@ -0,0 +1,7 @@
package prog8.code.core
class InternalCompilerException(message: String?) : Exception(message)
class AssemblyError(msg: String) : RuntimeException(msg)
class ErrorsReportedException(message: String?) : Exception(message)

View File

@ -0,0 +1,12 @@
package prog8.code.core
interface IAssemblyGenerator {
fun compileToAssembly(): IAssemblyProgram?
}
interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions): Boolean
}
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"

View File

@ -0,0 +1,11 @@
package prog8.code.core
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
val supportedEncodings: Set<Encoding>
val defaultEncoding: Encoding
override fun encodeString(str: String, encoding: Encoding): List<UByte>
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String
}

View File

@ -1,7 +1,4 @@
package prog8.compilerinterface
import prog8.ast.base.Position
package prog8.code.core
interface IErrorReporter {
fun err(msg: String, position: Position)
@ -10,7 +7,6 @@ interface IErrorReporter {
fun report()
fun finalizeNumErrors(numErrors: Int, numWarnings: Int) {
if(numErrors>0)
throw AbortCompilation("There are $numErrors errors and $numWarnings warnings.")
throw ErrorsReportedException("There are $numErrors errors and $numWarnings warnings.")
}
}

View File

@ -1,4 +1,4 @@
package prog8.compilerinterface
package prog8.code.core
import java.nio.file.Path
@ -10,18 +10,17 @@ interface IMachineFloat {
enum class CpuType {
CPU6502,
CPU65c02
CPU65c02,
VIRTUAL
}
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: UInt
val ESTACK_HI: UInt
val BASIC_LOAD_ADDRESS : UInt
val RAW_LOAD_ADDRESS : UInt
val PROGRAM_LOAD_ADDRESS : UInt
val opcodeNames: Set<String>
var zeropage: Zeropage

View File

@ -0,0 +1,6 @@
package prog8.code.core
interface IMemSizer {
fun memorySize(dt: DataType): Int
fun memorySize(arrayDt: DataType, numElements: Int): Int
}

View File

@ -0,0 +1,14 @@
package prog8.code.core
enum class Encoding(val prefix: String) {
DEFAULT("default"), // depends on compilation target
PETSCII("petscii"), // c64/c128/cx16
SCREENCODES("sc"), // c64/c128/cx16
ATASCII("atascii"), // atari
ISO("iso") // cx16
}
interface IStringEncoding {
fun encodeString(str: String, encoding: Encoding): List<UByte>
fun decodeString(bytes: List<UByte>, encoding: Encoding): String
}

View File

@ -0,0 +1,18 @@
package prog8.code.core
val AssociativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
val LogicalOperators = setOf("and", "or", "xor", "not")
val BitwiseOperators = setOf("&", "|", "^")
fun invertedComparisonOperator(operator: String) =
when (operator) {
"==" -> "!="
"!=" -> "=="
"<" -> ">="
">" -> "<="
"<=" -> ">"
">=" -> "<"
else -> null
}

View File

@ -0,0 +1,16 @@
package prog8.code.core
import kotlin.io.path.Path
import kotlin.io.path.absolute
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 {
val path = Path(file).absolute().normalize()
return "file://$path:$line:$startCol:"
}
companion object {
val DUMMY = Position("<dummy>", 0, 0, 0)
}
}

View File

@ -0,0 +1,3 @@
package prog8.code.core
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?)

View File

@ -1,27 +1,20 @@
package prog8.parser
package prog8.code.core
import org.antlr.v4.runtime.CharStream
import org.antlr.v4.runtime.CharStreams
import java.io.File
import java.io.IOException
import java.nio.channels.Channels
import java.nio.charset.CodingErrorAction
import java.nio.charset.StandardCharsets
import java.nio.file.Path
import kotlin.io.path.*
import kotlin.io.path.Path
import kotlin.io.path.readText
const val internedStringsModuleName = "prog8_interned_strings"
/**
* Encapsulates - and ties together - actual source code (=text)
* and its [origin].
* Encapsulates - and ties together - actual source code (=text) and its [origin].
*/
sealed class SourceCode {
/**
* To be used *only* by the parser (as input to a TokenStream).
* DO NOT mess around with!
*/
internal abstract fun getCharStream(): CharStream
/**
* Whether this [SourceCode] instance was created as a [Resource]
*/
@ -32,6 +25,11 @@ sealed class SourceCode {
*/
abstract val isFromFilesystem: Boolean
/**
* The logical name of the source code unit. Usually the module's name.
*/
abstract val name: String
/**
* Where this [SourceCode] instance came from.
* This can be one of the following:
@ -44,11 +42,10 @@ sealed class SourceCode {
/**
* The source code as plain string.
*/
abstract fun readText(): String
abstract val text: String
/**
* Deliberately does NOT return the actual text.
* For this - if at all - use [getCharStream].
* Printable representation, deliberately does NOT return the actual text.
*/
final override fun toString() = "${this.javaClass.name}[${this.origin}]"
@ -69,43 +66,40 @@ sealed class SourceCode {
* Turn a plain String into a [SourceCode] object.
* [origin] will be something like `$stringSourcePrefix44c56085>`.
*/
class Text(val text: String): SourceCode() {
class Text(override val text: String): SourceCode() {
override val isFromResources = false
override val isFromFilesystem = false
override val origin = "$stringSourcePrefix${System.identityHashCode(text).toString(16)}>"
override fun getCharStream(): CharStream = CharStreams.fromString(text, origin)
override fun readText() = text
override val name = "<unnamed-text>"
}
/**
* Get [SourceCode] from the file represented by the specified Path.
* This does not actually *access* the file, but it does check
* whether it
* * exists
* * is a regular file (ie: not a directory)
* * and is actually readable
* This immediately reads the file fully into memory.
*
* [origin] will be the given path in absolute and normalized form.
* @throws NoSuchFileException if the file does not exist
* @throws AccessDeniedException if the given path points to a directory or the file is non-readable for some other reason
* @throws FileSystemException if the file cannot be read
*/
class File(path: Path): SourceCode() {
private val normalized = path.normalize()
init {
val file = normalized.toFile()
if (!path.exists())
throw NoSuchFileException(file)
if (path.isDirectory())
throw AccessDeniedException(file, reason = "Not a file but a directory")
if (!path.isReadable())
throw AccessDeniedException(file, reason = "Is not readable")
}
override val text: String
override val origin: String
override val name: String
override val isFromResources = false
override val isFromFilesystem = true
override val origin = relative(normalized).toString()
override fun getCharStream(): CharStream = CharStreams.fromPath(normalized)
override fun readText() = normalized.readText()
init {
val normalized = path.normalize()
origin = relative(normalized).toString()
try {
text = normalized.readText()
name = normalized.toFile().nameWithoutExtension
} catch (nfx: java.nio.file.NoSuchFileException) {
throw NoSuchFileException(normalized.toFile()).also { it.initCause(nfx) }
} catch (iox: IOException) {
throw FileSystemException(normalized.toFile()).also { it.initCause(iox) }
}
}
}
/**
@ -114,6 +108,12 @@ sealed class SourceCode {
class Resource(pathString: String): SourceCode() {
private val normalized = "/" + Path.of(pathString).normalize().toMutableList().joinToString("/")
override val isFromResources = true
override val isFromFilesystem = false
override val origin = "$libraryFilePrefix$normalized"
override val text: String
override val name: String
init {
val rscURL = object {}.javaClass.getResource(normalized)
if (rscURL == null) {
@ -123,32 +123,19 @@ sealed class SourceCode {
reason = "looked in resources rooted at $rscRoot"
)
}
}
override val isFromResources = true
override val isFromFilesystem = false
override val origin = "$libraryFilePrefix$normalized"
public override fun getCharStream(): CharStream {
val inpStr = object {}.javaClass.getResourceAsStream(normalized)!!
// CharStreams.fromStream() doesn't allow us to set the stream name properly, so we use a lower level api
val channel = Channels.newChannel(inpStr)
return CharStreams.fromChannel(channel, StandardCharsets.UTF_8, 4096, CodingErrorAction.REPLACE, origin, -1)
}
override fun readText(): String {
val stream = object {}.javaClass.getResourceAsStream(normalized)
return stream!!.bufferedReader().use { r -> r.readText() }
text = stream!!.reader().use { it.readText() }
name = Path.of(pathString).toFile().nameWithoutExtension
}
}
/**
* SourceCode for internally generated nodes (usually Modules)
*/
class Generated(name: String) : SourceCode() {
override fun getCharStream(): CharStream = throw IOException("generated code nodes doesn't have a stream to read")
class Generated(override val name: String) : SourceCode() {
override val isFromResources: Boolean = false
override val isFromFilesystem: Boolean = false
override val origin: String = name
override fun readText() = throw IOException("generated code nodes don't have a text representation")
override val text: String = "<generated code node, no text representation>"
}
}

View File

@ -0,0 +1,120 @@
package prog8.code.core
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
class ZeropageAllocationError(message: String) : Exception(message)
abstract class Zeropage(protected val options: CompilationOptions) {
abstract val SCRATCH_B1 : UInt // temp storage for a single byte
abstract val SCRATCH_REG : UInt // temp storage for a register, must be B1+1
abstract val SCRATCH_W1 : UInt // temp storage 1 for a word $fb+$fc
abstract val SCRATCH_W2 : UInt // temp storage 2 for a word $fb+$fc
data class ZpAllocation(val address: UInt, val dt: DataType, val size: Int)
// the variables allocated into Zeropage.
// name (scoped) ==> pair of address to (Datatype + bytesize)
val allocatedVariables = mutableMapOf<List<String>, ZpAllocation>()
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
fun removeReservedFromFreePool() {
synchronized(this) {
for (reserved in options.zpReserved)
reserve(reserved)
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1 + 1u, SCRATCH_W2, SCRATCH_W2 + 1u))
}
}
fun availableBytes() = if(options.zeropage== ZeropageType.DONTUSE) 0 else free.size
fun hasByteAvailable() = if(options.zeropage== ZeropageType.DONTUSE) false else free.isNotEmpty()
fun hasWordAvailable(): Boolean {
if(options.zeropage== ZeropageType.DONTUSE)
return false
return free.windowed(2).any { it[0] == it[1] - 1u }
}
fun allocate(name: List<String>,
datatype: DataType,
numElements: Int?,
position: Position?,
errors: IErrorReporter
): Result<Pair<UInt, Int>, ZeropageAllocationError> {
require(name.isEmpty() || name !in allocatedVariables) {"name can't be allocated twice"}
if(options.zeropage== ZeropageType.DONTUSE)
return Err(ZeropageAllocationError("zero page usage has been disabled"))
val size: Int =
when (datatype) {
in IntegerDatatypes -> options.compTarget.memorySize(datatype)
DataType.STR, in ArrayDatatypes -> {
val memsize = options.compTarget.memorySize(datatype, numElements!!)
if(position!=null)
errors.warn("allocating a large value in zeropage; str/array $memsize bytes", position)
else
errors.warn("$name: allocating a large value in zeropage; str/array $memsize bytes", Position.DUMMY)
memsize
}
DataType.FLOAT -> {
if (options.floats) {
val memsize = options.compTarget.memorySize(DataType.FLOAT)
if(position!=null)
errors.warn("allocating a large value in zeropage; float $memsize bytes", position)
else
errors.warn("$name: allocating a large value in zeropage; float $memsize bytes", Position.DUMMY)
memsize
} else return Err(ZeropageAllocationError("floating point option not enabled"))
}
else -> return Err(ZeropageAllocationError("cannot put datatype $datatype in zeropage"))
}
synchronized(this) {
if(free.size > 0) {
if(size==1) {
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if(oneSeparateByteFree(candidate))
return Ok(Pair(makeAllocation(candidate, 1, datatype, name), 1))
}
return Ok(Pair(makeAllocation(free[0], 1, datatype, name), 1))
}
for(candidate in free.minOrNull()!! .. free.maxOrNull()!!+1u) {
if (sequentialFree(candidate, size))
return Ok(Pair(makeAllocation(candidate, size, datatype, name), size))
}
}
}
return Err(ZeropageAllocationError("no more free space in ZP to allocate $size sequential bytes"))
}
private fun reserve(range: UIntRange) = free.removeAll(range)
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: List<String>): UInt {
require(size>=0)
free.removeAll(address until address+size.toUInt())
if(name.isNotEmpty()) {
allocatedVariables[name] = when(datatype) {
in NumericDatatypes -> ZpAllocation(address, datatype, size) // numerical variables in zeropage never have an initial value here because they are set in separate initializer assignments
DataType.STR -> ZpAllocation(address, datatype, size)
in ArrayDatatypes -> ZpAllocation(address, datatype, size)
else -> throw AssemblyError("invalid dt")
}
}
return address
}
private fun oneSeparateByteFree(address: UInt) = address in free && address-1u !in free && address+1u !in free
private fun sequentialFree(address: UInt, size: Int): Boolean {
require(size>0)
return free.containsAll((address until address+size.toUInt()).toList())
}
}

View File

@ -0,0 +1,28 @@
package prog8.code.target
import prog8.code.core.*
import prog8.code.target.atari.AtariMachineDefinition
class AtariTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = AtariMachineDefinition()
override val supportedEncodings = setOf(Encoding.ATASCII)
override val defaultEncoding = Encoding.ATASCII
companion object {
const val NAME = "atari"
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
override fun memorySize(arrayDt: DataType, numElements: Int) =
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
}

View File

@ -0,0 +1,20 @@
package prog8.code.target
import prog8.code.core.Encoding
import prog8.code.core.ICompilationTarget
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.target.c128.C128MachineDefinition
import prog8.code.target.cbm.CbmMemorySizer
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C128MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
const val NAME = "c128"
}
}

View File

@ -0,0 +1,20 @@
package prog8.code.target
import prog8.code.core.Encoding
import prog8.code.core.ICompilationTarget
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.target.c64.C64MachineDefinition
import prog8.code.target.cbm.CbmMemorySizer
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C64MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES)
override val defaultEncoding = Encoding.PETSCII
companion object {
const val NAME = "c64"
}
}

View File

@ -0,0 +1,20 @@
package prog8.code.target
import prog8.code.core.Encoding
import prog8.code.core.ICompilationTarget
import prog8.code.core.IMemSizer
import prog8.code.core.IStringEncoding
import prog8.code.target.cbm.CbmMemorySizer
import prog8.code.target.cx16.CX16MachineDefinition
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = CX16MachineDefinition()
override val supportedEncodings = setOf(Encoding.PETSCII, Encoding.SCREENCODES, Encoding.ISO)
override val defaultEncoding = Encoding.PETSCII
companion object {
const val NAME = "cx16"
}
}

View File

@ -0,0 +1,39 @@
package prog8.code.target
import com.github.michaelbull.result.fold
import prog8.code.core.Encoding
import prog8.code.core.IStringEncoding
import prog8.code.core.InternalCompilerException
import prog8.code.target.cbm.AtasciiEncoding
import prog8.code.target.cbm.IsoEncoding
import prog8.code.target.cbm.PetsciiEncoding
object Encoder: IStringEncoding {
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
Encoding.ISO -> IsoEncoding.encode(str)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
val decoded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
Encoding.ISO -> IsoEncoding.decode(bytes)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
return decoded.fold(
failure = { throw it },
success = { it }
)
}
}

View File

@ -0,0 +1,27 @@
package prog8.code.target
import prog8.code.core.*
import prog8.code.target.virtual.VirtualMachineDefinition
class VMTarget: ICompilationTarget, IStringEncoding by Encoder, IMemSizer {
override val name = NAME
override val machine = VirtualMachineDefinition()
override val supportedEncodings = setOf(Encoding.ISO)
override val defaultEncoding = Encoding.ISO
companion object {
const val NAME = "virtual"
}
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
override fun memorySize(arrayDt: DataType, numElements: Int) =
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
}

View File

@ -0,0 +1,65 @@
package prog8.code.target.atari
import prog8.code.core.*
import prog8.code.target.c64.normal6502instructions
import java.nio.file.Path
class AtariMachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = 9.999999999e97
override val FLOAT_MAX_NEGATIVE = -9.999999999e97
override val FLOAT_MEM_SIZE = 6
override val PROGRAM_LOAD_ADDRESS = 0x2000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive // TODO
override val ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = TODO("float from number")
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.output == OutputType.XEX)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulatorName: String
val cmdline: List<String>
when(selectedEmulator) {
1 -> {
emulatorName = "atari800"
cmdline = listOf(emulatorName, "-xl", "-xl-rev", "2", "-nobasic", "-run", "${programNameWithPath}.xex")
}
2 -> {
emulatorName = "altirra"
cmdline = listOf("Altirra64.exe", "${programNameWithPath.normalize()}.xex")
}
else -> {
System.err.println("Atari target only supports atari800 and altirra emulators.")
return
}
}
// TODO monlist?
println("\nStarting Atari800XL emulator $emulatorName...")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu // TODO
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = AtariZeropage(compilerOptions)
}
override val opcodeNames = normal6502instructions
}

View File

@ -0,0 +1,45 @@
package prog8.code.target.atari
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
class AtariZeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0xcbu // temp storage for a single byte
override val SCRATCH_REG = 0xccu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xcdu // temp storage 1 for a word $cd+$ce
override val SCRATCH_W2 = 0xcfu // temp storage 2 for a word $cf+$d0 TODO is $d0 okay to use?
init {
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
ZeropageType.DONTUSE
))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
when (options.zeropage) {
ZeropageType.FULL -> {
// TODO all atari usable zero page locations, except the ones used by the system's IRQ routine
free.addAll(0x00u..0xffu)
// TODO atari free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x80u..0xffu) // TODO
}
ZeropageType.BASICSAFE,
ZeropageType.FLOATSAFE -> {
free.addAll(0x80u..0xffu) // TODO
free.removeAll(0xd4u .. 0xefu) // floating point storage
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
}
removeReservedFromFreePool()
}
}

View File

@ -0,0 +1,55 @@
package prog8.code.target.c128
import prog8.code.core.*
import prog8.code.target.c64.normal6502instructions
import prog8.code.target.cbm.Mflpt5
import java.nio.file.Path
class C128MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
override val ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The c128 target only supports the main emulator (Vice).")
return
}
println("\nStarting C-128 emulator x128...")
val viceMonlist = viceMonListName(programNameWithPath.toString())
val cmdline = listOf("x128", "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.prg")
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C128Zeropage(compilerOptions)
}
override val opcodeNames = normal6502instructions
}

View File

@ -0,0 +1,43 @@
package prog8.code.target.c128
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
class C128Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x9bu // temp storage for a single byte
override val SCRATCH_REG = 0x9cu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
init {
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
ZeropageType.DONTUSE
))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
when (options.zeropage) {
ZeropageType.FULL -> {
// TODO all c128 usable zero page locations, except the ones used by the system's IRQ routine
free.addAll(0x0au..0xffu) // TODO c128 what about $02-$09?
// TODO c128 free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // these are updated by IRQ
}
ZeropageType.KERNALSAFE,
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE -> {
free.clear() // TODO c128 usable zero page addresses
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
}
removeReservedFromFreePool()
}
}

View File

@ -0,0 +1,76 @@
package prog8.code.target.c64
import prog8.code.core.*
import prog8.code.target.cbm.Mflpt5
import java.io.IOException
import java.nio.file.Path
class C64MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU6502
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x0801u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The c64 target only supports the main emulator (Vice).")
return
}
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val viceMonlist = viceMonListName(programNameWithPath.toString())
val cmdline = listOf(emulator, "-silent", "-moncommands", viceMonlist,
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.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 isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions)
}
override val opcodeNames = normal6502instructions
}
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
internal val normal6502instructions = 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")

View File

@ -0,0 +1,74 @@
package prog8.code.target.c64
import prog8.code.core.CompilationOptions
import prog8.code.core.InternalCompilerException
import prog8.code.core.Zeropage
import prog8.code.core.ZeropageType
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
override val SCRATCH_REG = 0x03u // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fd+$fe
init {
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
ZeropageType.DONTUSE
))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x02u..0xffu)
free.removeAll(setOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_W1, SCRATCH_W1+1u, SCRATCH_W2, SCRATCH_W2+1u))
free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // 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 'kernal work storage area'
).map{it.toUInt()})
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zeropage locations used for floating point operations from the free list
free.removeAll(listOf(
0x22, 0x23, 0x24, 0x25,
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
).map{it.toUInt()})
}
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).map{it.toUInt()})
} else {
// don't use the zeropage at all
free.clear()
}
}
removeReservedFromFreePool()
}
}

View File

@ -0,0 +1,214 @@
package prog8.code.target.cbm
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.io.CharConversionException
object AtasciiEncoding {
private val decodeTable: CharArray = charArrayOf(
// $00
'♥',
'├',
'\uf130', // 🮇 0x02 -> RIGHT ONE QUARTER BLOCK (CUS)
'┘',
'┤',
'┐',
'',
'╲',
'◢',
'▗',
'◣',
'▝',
'▘',
'\uf132', // 🮂 0x1d -> UPPER ONE QUARTER BLOCK (CUS)
'▂',
'▖',
// $10
'♣',
'┌',
'─',
'┼',
'•',
'▄',
'▎',
'┬',
'┴',
'▌',
'└',
'\u001b', // $1b = escape
'\ufffe', // UNDEFINED CHAR. $1c = cursor up
'\ufffe', // UNDEFINED CHAR. $1d = cursor down
'\ufffe', // UNDEFINED CHAR. $1e = cursor left
'\ufffe', // UNDEFINED CHAR. $1f = cursor right
// $20
' ',
'!',
'"',
'#',
'$',
'%',
'&',
'\'',
'(',
')',
'*',
'+',
',',
'-',
'.',
'/',
// $30
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
':',
';',
'<',
'=',
'>',
'?',
// $40
'@',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
// $50
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'[',
'\\',
']',
'^',
'_',
// $60
'♦',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
// $70
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'♠',
'|',
'\u000c', // $7d -> FORM FEED (CLEAR SCREEN)
'\u0008', // $7e -> BACKSPACE
'\u0009', // $7f -> TAB
// $80-$ff are reversed video characters + various special characters.
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $90
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe',
'\ufffe',
'\ufffe',
'\n', // $9b -> EOL/RETURN
'\ufffe', // UNDEFINED $9c = DELETE LINE
'\ufffe', // UNDEFINED $9d = INSERT LINE
'\ufffe', // UNDEFINED $9e = CLEAR TAB STOP
'\ufffe', // UNDEFINED $9f = SET TAB STOP
// $a0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $b0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $c0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $d0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $e0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
// $f0
'\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe', '\ufffe',
'\ufffe',
'\ufffe',
'\ufffe',
'\ufffe',
'\ufffe',
'\u0007', // $fd = bell/beep
'\u007f', // $fe = DELETE
'\ufffe' // UNDEFINED $ff = INSERT
)
private val encodeTable = decodeTable.withIndex().associate{it.value to it.index}
fun encode(str: String): Result<List<UByte>, CharConversionException> {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
}
else -> encodeTable.getValue(chr).toUByte()
}
}
return Ok(mapped)
}
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
return Ok(bytes.map { decodeTable[it.toInt()] }.joinToString(""))
}
}

View File

@ -0,0 +1,18 @@
package prog8.code.target.cbm
import prog8.code.core.*
internal object CbmMemorySizer: IMemSizer {
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> Mflpt5.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
override fun memorySize(arrayDt: DataType, numElements: Int) =
memorySize(ArrayToElementTypes.getValue(arrayDt)) * numElements
}

View File

@ -0,0 +1,37 @@
package prog8.code.target.cbm
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
object IsoEncoding {
val charset: Charset = Charset.forName("ISO-8859-15")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
}
else -> charset.encode(chr.toString())[0].toUByte()
}
}
Ok(mapped)
} catch (ce: CharConversionException) {
Err(ce)
}
}
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}
}
}

View File

@ -0,0 +1,78 @@
package prog8.code.target.cbm
import prog8.code.core.IMachineFloat
import prog8.code.core.InternalCompilerException
import kotlin.math.absoluteValue
import kotlin.math.pow
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte): IMachineFloat {
companion object {
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 FLOAT_MEM_SIZE = 5
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
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 InternalCompilerException("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 InternalCompilerException("floating point overflow: $this")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toUByte(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toUByte(),
(mantLong.and(0x00ff0000L) ushr 16).toUByte(),
(mantLong.and(0x0000ff00L) ushr 8).toUByte(),
(mantLong.and(0x000000ffL)).toUByte()
)
}
}
}
}
override fun toDouble(): Double {
if (this == zero) return 0.0
val exp = b0.toInt() - 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,15 +1,14 @@
package prog8.compiler.target.cbm
package prog8.code.target.cbm
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import prog8.ast.antlr.escape
import java.io.CharConversionException
object Petscii {
object PetsciiEncoding {
// decoding: from Petscii/Screencodes (0-255) to unicode
// character tables used from https://github.com/dj51d/cbmcodecs
// character tables used from https://github.com/irmen/cbmcodecs2
private val decodingPetsciiLowercase = charArrayOf(
'\u0000', // 0x00 -> \u0000
@ -159,7 +158,7 @@ object Petscii {
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
'\uf11e', //  0x91 -> CURSOR UP (CUS)
'\uf11b', //  0x92 -> REVERSE VIDEO OFF (CUS)
'\u000c', // 0x93 -> FORM FEED
'\u000c', // 0x93 -> FORM FEED (CLEAR SCREEN)
'\uf121', //  0x94 -> INSERT (CUS)
'\uf106', // 0x95 -> BROWN COLOR SWITCH (CUS)
'\uf107', // 0x96 -> LIGHT RED COLOR SWITCH (CUS)
@ -195,7 +194,7 @@ object Petscii {
'\u258e', // ▎ 0xB4 -> LEFT ONE QUARTER BLOCK
'\u258d', // ▍ 0xB5 -> LEFT THREE EIGTHS BLOCK
'\uf131', //  0xB6 -> RIGHT THREE EIGHTHS BLOCK (CUS)
'\uf132', // 0xB7 -> UPPER ONE QUARTER BLOCK (CUS)
'\uf132', // 🮂 0xB7 -> UPPER ONE QUARTER BLOCK (CUS)
'\uf133', //  0xB8 -> UPPER THREE EIGHTS BLOCK (CUS)
'\u2583', // ▃ 0xB9 -> LOWER THREE EIGHTHS BLOCK
'\u2713', // ✓ 0xBA -> CHECK MARK
@ -246,7 +245,7 @@ object Petscii {
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
'\uf12f', //  0xE8 -> LOWER HALF BLOCK MEDIUM SHADE (CUS)
'\uf13a', //  0xE9 -> MEDIUM SHADE SLASHED RIGHT (CUS)
'\uf130', // 0xEA -> RIGHT ONE QUARTER BLOCK (CUS)
'\uf130', // 🮇 0xEA -> RIGHT ONE QUARTER BLOCK (CUS)
'\u251c', // ├ 0xEB -> BOX DRAWINGS LIGHT VERTICAL AND RIGHT
'\u2597', // ▗ 0xEC -> QUADRANT LOWER RIGHT
'\u2514', // └ 0xED -> BOX DRAWINGS LIGHT UP AND RIGHT
@ -418,7 +417,7 @@ object Petscii {
'\uf105', // 0x90 -> BLACK COLOR SWITCH (CUS)
'\uf11e', // 0x91 -> CURSOR UP (CUS)
'\uf11b', // 0x92 -> REVERSE VIDEO OFF (CUS)
'\u000c', // 0x93 -> FORM FEED
'\u000c', // 0x93 -> FORM FEED (CLEAR SCREEN)
'\uf121', // 0x94 -> INSERT (CUS)
'\uf106', // 0x95 -> BROWN COLOR SWITCH (CUS)
'\uf107', // 0x96 -> LIGHT RED COLOR SWITCH (CUS)
@ -1077,7 +1076,7 @@ object Petscii {
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Petscii character for '${escape(chr.toString())}' (${chr.code})")
throw CharConversionException("no ${case}Petscii character for '${chr}' (${chr.code})")
}
}
}
@ -1095,13 +1094,17 @@ object Petscii {
}
}
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): String {
return petscii.map {
val code = it.toInt()
if(code<0 || code>= decodingPetsciiLowercase.size)
throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}")
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
}.joinToString("")
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): Result<String, CharConversionException> {
return try {
Ok(petscii.map {
val code = it.toInt()
if(code<0 || code>= decodingPetsciiLowercase.size)
throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}")
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
}.joinToString(""))
} catch(ce: CharConversionException) {
return Err(ce)
}
}
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
@ -1116,7 +1119,7 @@ object Petscii {
}
else -> {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '${escape(chr.toString())}' (${chr.code})")
throw CharConversionException("no ${case}Screencode character for '${chr}' (${chr.code})")
}
}
}
@ -1134,13 +1137,17 @@ object Petscii {
}
}
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): String {
return screencode.map {
val code = it.toInt()
if(code<0 || code>= decodingScreencodeLowercase.size)
throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}")
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
}.joinToString("")
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): Result<String, CharConversionException> {
return try {
Ok(screencode.map {
val code = it.toInt()
if(code<0 || code>= decodingScreencodeLowercase.size)
throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}")
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
}.joinToString(""))
} catch(ce: CharConversionException) {
Err(ce)
}
}
fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> {

View File

@ -0,0 +1,77 @@
package prog8.code.target.cx16
import prog8.code.core.*
import prog8.code.target.cbm.Mflpt5
import java.nio.file.Path
class CX16MachineDefinition: IMachineDefinition {
override val cpu = CpuType.CPU65c02
override val FLOAT_MAX_POSITIVE = Mflpt5.FLOAT_MAX_POSITIVE
override val FLOAT_MAX_NEGATIVE = Mflpt5.FLOAT_MAX_NEGATIVE
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
override val PROGRAM_LOAD_ADDRESS = 0x0801u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x0400u // $0400-$04ff inclusive
override val ESTACK_HI = 0x0500u // $0500-$05ff inclusive
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulator: String
val extraArgs: List<String>
when(selectedEmulator) {
1 -> {
emulator = "x16emu"
extraArgs = emptyList()
}
2 -> {
emulator = "box16"
extraArgs = listOf("-sym", viceMonListName(programNameWithPath.toString()))
}
else -> {
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
return
}
}
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process = processb.start()
process.waitFor()
}
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions)
}
// 65c02 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")
}

View File

@ -0,0 +1,60 @@
package prog8.code.target.cx16
import prog8.code.core.*
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
init {
if (options.floats && options.zeropage !in arrayOf(
ZeropageType.FLOATSAFE,
ZeropageType.BASICSAFE,
ZeropageType.DONTUSE
))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
synchronized(this) {
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.FLOATSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xd4u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
}
removeReservedFromFreePool()
// note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// however, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
}
}
}
}

View File

@ -0,0 +1,48 @@
package prog8.code.target.virtual
import prog8.code.core.CompilationOptions
import prog8.code.core.CpuType
import prog8.code.core.IMachineDefinition
import prog8.code.core.Zeropage
import java.io.File
import java.nio.file.Path
class VirtualMachineDefinition: IMachineDefinition {
override val cpu = CpuType.VIRTUAL
override val FLOAT_MAX_POSITIVE = Float.MAX_VALUE.toDouble()
override val FLOAT_MAX_NEGATIVE = -Float.MAX_VALUE.toDouble()
override val FLOAT_MEM_SIZE = 4 // 32-bits floating point
override val PROGRAM_LOAD_ADDRESS = 0u // not actually used
override val ESTACK_LO = 0u // not actually used
override val ESTACK_HI = 0u // not actually used
override lateinit var zeropage: Zeropage // not actually used
override fun getFloat(num: Number) = TODO("float from number")
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return listOf("syslib")
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
println("\nStarting Virtual Machine...")
// to not have external module dependencies we launch the virtual machine via reflection
val vm = Class.forName("prog8.vm.VmRunner").getDeclaredConstructor().newInstance() as IVirtualMachineRunner
val source = File("$programNameWithPath.p8virt").readText()
vm.runProgram(source, true)
}
override fun isIOAddress(address: UInt): Boolean = false
override fun initializeZeropage(compilerOptions: CompilationOptions) {}
override val opcodeNames = emptySet<String>()
}
interface IVirtualMachineRunner {
fun runProgram(source: String, throttle: Boolean)
}

View File

@ -0,0 +1,47 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':codeAst')
implementation project(':codeCore')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
}
// note: there are no unit tests in this module!

View File

@ -0,0 +1,17 @@
<?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$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="codeAst" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>

View File

@ -1,16 +1,16 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.cpu6502
import prog8.ast.Program
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.VarDecl
import prog8.compilerinterface.IMachineDefinition
import prog8.ast.statements.VarDeclType
import prog8.code.core.IMachineDefinition
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
var numberOfOptimizations = 0
@ -64,6 +64,11 @@ fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, pr
return numberOfOptimizations
}
private fun String.isBranch() = this.startsWith("b")
private fun String.isStoreReg() = this.startsWith("sta") || this.startsWith("sty") || this.startsWith("stx")
private fun String.isStoreRegOrZero() = this.isStoreReg() || this.startsWith("stz")
private fun String.isLoadReg() = this.startsWith("lda") || this.startsWith("ldy") || this.startsWith("ldx")
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
@ -196,9 +201,9 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
sta A1
sty A2
*/
if(first.startsWith("st") && second.startsWith("st")
&& third.startsWith("ld") && fourth.startsWith("ld")
&& fifth.startsWith("st") && sixth.startsWith("st")) {
if(first.isStoreReg() && second.isStoreReg()
&& third.isLoadReg() && fourth.isLoadReg()
&& fifth.isStoreReg() && sixth.isStoreReg()) {
val reg1 = first[2]
val reg2 = second[2]
val reg3 = third[2]
@ -227,8 +232,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
lda A1 ; can be removed
ldy A2 ; can be removed if not followed by a branch instuction
*/
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
&& third.startsWith("ld") && fourth.startsWith("ld")) {
if(!overlappingMods && first.isStoreReg() && second.isStoreReg()
&& third.isLoadReg() && fourth.isLoadReg()) {
val reg1 = first[2]
val reg2 = second[2]
val reg3 = third[2]
@ -249,13 +254,14 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
}
}
}
/*
sta A1
sty A2
sty A2 ; ... or stz
lda A1 ; can be removed if not followed by a branch instruction
*/
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")
&& third.startsWith("ld") && !fourth.startsWith("b")) {
if(!overlappingMods && first.isStoreReg() && second.isStoreRegOrZero()
&& third.isLoadReg() && !fourth.isBranch()) {
val reg1 = first[2]
val reg3 = third[2]
if(reg1==reg3) {
@ -276,7 +282,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
ldy A1 ; make tay
sta A1 ; remove
*/
if(!overlappingMods && first.startsWith("sta") && second.startsWith("ld")
if(!overlappingMods && first.startsWith("sta") && second.isLoadReg()
&& third.startsWith("sta") && second.length>4) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
@ -293,10 +299,10 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
}
/*
sta A
sta A
sta A ; or stz double store, remove this first one
sta A ; or stz
*/
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")) {
if(!overlappingMods && first.isStoreRegOrZero() && second.isStoreRegOrZero()) {
if(first[2]==second[2]) {
val firstvalue = first.substring(4)
val secondvalue = second.substring(4)
@ -304,7 +310,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[0].index, true, null))
}
}
}
@ -333,7 +339,7 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
) {
val third = lines[3].value.trimStart()
val attemptRemove =
if(third.startsWith("b")) {
if(third.isBranch()) {
// a branch instruction follows, we can only remove the load instruction if
// another load instruction of the same register precedes the store instruction
// (otherwise wrong cpu flags are used)
@ -400,7 +406,7 @@ private fun getAddressArg(line: String, program: Program): UInt? {
when(decl.type){
VarDeclType.VAR -> null
VarDeclType.CONST,
VarDeclType.MEMORY -> (decl.value as NumericLiteralValue).number.toUInt()
VarDeclType.MEMORY -> (decl.value as NumericLiteral).number.toUInt()
}
}
else null

View File

@ -1,13 +1,15 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.cpu6502
import prog8.ast.base.Cx16VirtualRegisters
import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.*
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.expressions.ArrayIndexedExpression
import prog8.ast.expressions.BuiltinFunctionCall
import prog8.ast.expressions.Expression
import prog8.ast.statements.Subroutine
import prog8.code.core.Cx16VirtualRegisters
import prog8.code.core.RegisterOrPair
import prog8.code.core.RegisterOrStatusflag
internal fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
val order = mutableListOf<Int>()
// order is:
// 1) cx16 virtual word registers,
@ -35,7 +37,7 @@ internal fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
return order
}
internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
paramRegisters: List<RegisterOrStatusflag>): Boolean {
fun isClobberRisk(expr: Expression): Boolean {
when (expr) {
@ -44,10 +46,10 @@ internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
}
}
is FunctionCall -> {
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
is BuiltinFunctionCall -> {
if (expr.name == "lsb" || expr.name == "msb")
return isClobberRisk(expr.args[0])
if (expr.target.nameInSource == listOf("mkword"))
if (expr.name == "mkword")
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
return !expr.isSimple
}

View File

@ -0,0 +1,146 @@
package prog8.codegen.cpu6502
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError
import prog8.code.core.*
import java.io.File
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
internal class AssemblyProgram(
override val name: String,
outputDir: Path,
private val compTarget: ICompilationTarget) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg") // CBM prg executable program
private val xexFile = outputDir.resolve("$name.xex") // Atari xex executable program
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve(viceMonListName(name))
private val listFile = outputDir.resolve("$name.list")
override fun assemble(options: CompilationOptions): Boolean {
val assemblerCommand: List<String>
when (compTarget.name) {
in setOf("c64", "c128", "cx16") -> {
// CBM machines .prg generation.
// 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",
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
)
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating prg for target ${compTarget.name}.")
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${compTarget.name}.")
binFile
}
else -> throw AssemblyError("invalid output type")
}
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
assemblerCommand = command
}
"atari" -> {
// Atari800XL .xex generation.
// TODO are these options okay?
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
"--no-monitor"
)
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
val outFile = when (options.output) {
OutputType.XEX -> {
command.add("--atari-xex")
println("\nCreating xex for target ${compTarget.name}.")
xexFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${compTarget.name}.")
binFile
}
else -> throw AssemblyError("invalid output type")
}
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
assemblerCommand = command
}
else -> throw AssemblyError("invalid compilation target")
}
val proc = ProcessBuilder(assemblerCommand).inheritIO().start()
val result = proc.waitFor()
if (result == 0 && compTarget.name!="atari") {
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
return result==0
}
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 match = pattern.matchEntire(line)
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")
}
}
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
return com.github.michaelbull.result.runCatching {
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").text
}.mapError { NoSuchFileException(File(filename)) }
} else {
val sib = Path(source.origin).resolveSibling(filename)
if (sib.isRegularFile())
Ok(SourceCode.File(sib).text)
else
Ok(SourceCode.File(Path(filename)).text)
}
}

View File

@ -1,32 +1,73 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.cpu6502
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionCallStatement
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.cpu6502.codegen.assignment.*
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.FSignature
import prog8.compilerinterface.subroutineFloatEvalResultVar2
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.*
import prog8.compiler.BuiltinFunctions
import prog8.compiler.FSignature
import prog8.compiler.builtinFunctionReturnType
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
internal class BuiltinFunctionsAsmGen(private val program: Program,
private val asmgen: AsmGen,
private val assignAsmGen: AssignmentAsmGen,
private val allocations: VariableAllocator) {
internal fun translateFunctioncallExpression(fcall: BuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val func = BuiltinFunctions.getValue(fcall.target.nameInSource.single())
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
}
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
internal fun translateFunctioncallStatement(fcall: BuiltinFunctionCallStatement) {
val func = BuiltinFunctions.getValue(fcall.name)
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
}
internal fun translateFunctionCallWithFirstArg(bfc: IFunctionCall, singleArg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
val name = bfc.target.nameInSource.single()
val func = BuiltinFunctions.getValue(name)
val argExpression =
when(singleArg.kind) {
SourceStorageKind.LITERALNUMBER -> singleArg.number!!
SourceStorageKind.EXPRESSION -> singleArg.expression!!
SourceStorageKind.ARRAY -> singleArg.array!!
else -> {
// TODO make it so that we can assign efficiently from something else as an expression....namely: register(s)
// this is useful in pipe expressions for instance, to skip the use of a temporary variable
// but for now, just assign it to a temporary variable and use that as a source
// Idea: to do this without having to rewrite every single function in translateFunctioncall(),
// hack a special IdentifierReference like "!6502.A/X/Y/AX/AY/XY" to reference a cpu register
val tempvar = asmgen.getTempVarName(singleArg.datatype)
val assignTempvar = AsmAssignment(
singleArg,
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, singleArg.datatype, scope, variableAsmName = asmgen.asmVariableName(tempvar)),
false, program.memsizer, Position.DUMMY
)
assignAsmGen.translateNormalAssignment(assignTempvar)
// now use an expression to assign this tempvar
val ident = IdentifierReference(tempvar, Position.DUMMY)
ident.linkParents(scope)
ident
}
}
val argExpressions = mutableListOf(argExpression)
val fcall = BuiltinFunctionCall(IdentifierReference(listOf(name), Position.DUMMY), argExpressions, Position.DUMMY)
fcall.linkParents(scope)
translateFunctioncall(fcall, func, discardResult = false, resultToStack = false, null)
return if(isStatement) {
DataType.UNDEFINED
} else {
builtinFunctionReturnType(func.name).getOrElse { throw AssemblyError("unknown dt") }
}
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if (discardResult && func.pure)
return // can just ignore the whole function call altogether
@ -42,18 +83,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"mkword" -> funcMkword(fcall, resultToStack, resultRegister)
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
"swap" -> funcSwap(fcall)
"min", "max" -> funcMinMax(fcall, func, resultToStack, resultRegister, sscope)
"sum" -> funcSum(fcall, resultToStack, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
"sin8", "sin8u", "sin16", "sin16u",
"sinr8", "sinr8u", "sinr16", "sinr16u",
"cos8", "cos8u", "cos16", "cos16u",
"cosr8", "cosr8u", "cosr16", "cosr16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, resultRegister, sscope)
"rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
"rol" -> funcRol(fcall)
@ -66,9 +97,22 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"peekw" -> funcPeekW(fcall, resultToStack, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"pokemon" -> { /* meme function */ }
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
"push", "pushw" -> funcPush(fcall, func)
"pop", "popw" -> funcPop(fcall, func)
"push" -> asmgen.pushCpuStack(DataType.UBYTE, fcall.args[0])
"pushw" -> asmgen.pushCpuStack(DataType.UWORD, fcall.args[0])
"pop" -> {
require(fcall.args[0] is IdentifierReference) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
}
asmgen.popCpuStack(DataType.UBYTE, (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!, (fcall as Node).definingSubroutine)
}
"popw" -> {
require(fcall.args[0] is IdentifierReference) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
}
asmgen.popCpuStack(DataType.UWORD, (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!, (fcall as Node).definingSubroutine)
}
"rsave" -> funcRsave()
"rsavex" -> funcRsaveX()
"rrestore" -> funcRrestore()
@ -131,119 +175,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
}
private fun funcPop(fcall: IFunctionCall, func: FSignature) {
// note: because A is pushed first so popped last, saving A is often not required here.
require(fcall.args[0] is IdentifierReference) {
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
}
val target = (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!
val parameter = target.subroutineParameter
if(parameter!=null) {
val sub = parameter.definingSubroutine!!
require(sub.isAsmSubroutine) {
"push/pop arg passing only supported on asmsubs ${(fcall as Node).position}"
}
val shouldKeepA = sub.asmParameterRegisters.any { it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY }
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
if(reg.statusflag!=null) {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG")
asmgen.out("""
clc
pla
beq +
sec
+""")
if(shouldKeepA)
asmgen.out(" lda P8ZP_SCRATCH_REG")
}
else {
if (func.name == "pop") {
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" pla")
RegisterOrPair.X -> asmgen.out(" plx")
RegisterOrPair.Y -> asmgen.out(" ply")
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
} else {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" pla")
RegisterOrPair.X -> {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
else
asmgen.out(" pla | tax")
}
RegisterOrPair.Y -> {
if(shouldKeepA)
asmgen.out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
else
asmgen.out(" pla | tay")
}
in Cx16VirtualRegisters -> asmgen.out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
} else {
// word pop
if (asmgen.isTargetCpu(CpuType.CPU65c02))
when (reg.registerOrPair) {
RegisterOrPair.AX -> asmgen.out(" plx | pla")
RegisterOrPair.AY -> asmgen.out(" ply | pla")
RegisterOrPair.XY -> asmgen.out(" ply | plx")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
else {
when (reg.registerOrPair) {
RegisterOrPair.AX -> asmgen.out(" pla | tax | pla")
RegisterOrPair.AY -> asmgen.out(" pla | tay | pla")
RegisterOrPair.XY -> asmgen.out(" pla | tay | pla | tax")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
asmgen.out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
}
}
} else {
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, target.datatype, (fcall as Node).definingSubroutine, variableAsmName = asmgen.asmVariableName(target.name))
if (func.name == "pop") {
asmgen.out(" pla")
asmgen.assignRegister(RegisterOrPair.A, tgt)
} else {
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" ply | pla")
else
asmgen.out(" pla | tay | pla")
asmgen.assignRegister(RegisterOrPair.AY, tgt)
}
}
}
private fun funcPush(fcall: IFunctionCall, func: FSignature) {
val signed = fcall.args[0].inferType(program).oneOf(DataType.BYTE, DataType.WORD)
if(func.name=="push") {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A, signed)
asmgen.out(" pha")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY, signed)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" pha | phy")
else
asmgen.out(" pha | tya | pha")
}
}
private fun funcCallFar(fcall: IFunctionCall) {
if(asmgen.options.compTarget !is Cx16Target)
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt()
@ -274,7 +207,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
.byte ${bank.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
}
is NumericLiteralValue -> {
is NumericLiteral -> {
asmgen.out("""
lda ${argAddrArg.number.toHex()}
jsr cx16.jsrfar
@ -288,7 +221,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
private fun funcCallRom(fcall: IFunctionCall) {
if(asmgen.options.compTarget !is Cx16Target)
if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callrom only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt()
@ -327,7 +260,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
pla
sta $01""")
}
is NumericLiteralValue -> {
is NumericLiteral -> {
asmgen.out("""
lda $01
pha
@ -347,8 +280,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcCmp(fcall: IFunctionCall) {
val arg1 = fcall.args[0]
val arg2 = fcall.args[1]
val dt1 = arg1.inferType(program).getOr(DataType.UNDEFINED)
val dt2 = arg2.inferType(program).getOr(DataType.UNDEFINED)
val dt1 = arg1.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val dt2 = arg2.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes) {
when (arg2) {
@ -356,12 +289,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
}
is NumericLiteralValue -> {
is NumericLiteral -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.number}")
asmgen.out(" cmp #${arg2.number.toInt()}")
}
is DirectMemoryRead -> {
if(arg2.addressExpression is NumericLiteralValue) {
if(arg2.addressExpression is NumericLiteral) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
} else {
@ -390,12 +323,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
cmp ${asmgen.asmVariableName(arg2)}
+""")
}
is NumericLiteralValue -> {
is NumericLiteral -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy #>${arg2.number}
cpy #>${arg2.number.toInt()}
bne +
cmp #<${arg2.number}
cmp #<${arg2.number.toInt()}
+""")
}
else -> {
@ -414,15 +347,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult || fcall !is FunctionCall)
if(discardResult || fcall !is BuiltinFunctionCall)
throw AssemblyError("should not discard result of memory allocation at $fcall")
val nameRef = fcall.args[0] as IdentifierReference
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
val size = (fcall.args[1] as NumericLiteralValue).number.toUInt()
val name = (fcall.args[0] as StringLiteral).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
val size = (fcall.args[1] as NumericLiteral).number.toUInt()
val align = (fcall.args[2] as NumericLiteral).number.toUInt()
val existingSize = asmgen.slabs[name]
if(existingSize!=null && existingSize!=size)
throw AssemblyError("memory slab '$name' already exists with a different size ($size) at ${fcall.position}")
val existing = allocations.getMemorySlab(name)
if(existing!=null && (existing.first!=size || existing.second!=align))
throw AssemblyError("memory slab '$name' already exists with a different size or alignment at ${fcall.position}")
val slabname = IdentifierReference(listOf("prog8_slabs", name), fcall.position)
slabname.linkParents(fcall)
@ -434,7 +368,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen)
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign)
asmgen.slabs[name] = size
allocations.allocateMemorySlab(name, size, align)
}
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
@ -447,23 +381,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun funcSinCosInt(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
else
when(func.name) {
"sin8", "sin8u", "sinr8", "sinr8u", "cos8", "cos8u", "cosr8", "cosr8u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
"sin16", "sin16u", "sinr16", "sinr16u", "cos16", "cos16u", "cosr16", "cosr16u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
}
}
private fun funcReverse(fcall: IFunctionCall) {
val variable = fcall.args.single()
if (variable is IdentifierReference) {
@ -546,8 +463,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.ror2_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteral).number
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
} else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
@ -589,14 +506,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.ror_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteral).number
asmgen.out(" ror ${number.toHex()}")
} else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
if(ptrAndIndex!=null) {
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine!!)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
@ -647,8 +564,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.rol2_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteral).number
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
} else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
@ -690,14 +607,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.rol_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteral).number
asmgen.out(" rol ${number.toHex()}")
} else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
if(ptrAndIndex!=null) {
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine!!)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
@ -742,16 +659,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.assignExpressionToVariable(indexer.indexExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
}
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
if(resultToStack)
asmgen.out(" jsr floats.func_${func.name}_stack")
else {
asmgen.out(" jsr floats.func_${func.name}_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
}
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program)
@ -789,92 +696,15 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
} else {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A | ldy #0")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A | ldy #0")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A | ldy #0")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
}
private fun funcMinMax(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_ub_stack")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_stack")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_stack")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack")
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_${function.name}_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt")
}
}
}
private fun funcSum(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_sum_ub_stack")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_sum_b_stack")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_stack")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_sum_w_stack")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_sum_f_stack")
else -> throw AssemblyError("weird type $dt")
}
} else {
when (dt.getOr(DataType.UNDEFINED)) {
DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_sum_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt")
}
}
}
private fun funcSwap(fcall: IFunctionCall) {
val first = fcall.args[0]
@ -920,8 +750,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// optimized simple case: swap two memory locations
if(first is DirectMemoryRead && second is DirectMemoryRead) {
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
val addr1 = (first.addressExpression as? NumericLiteral)?.number?.toHex()
val addr2 = (second.addressExpression as? NumericLiteral)?.number?.toHex()
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
val name2 = if(second.addressExpression is IdentifierReference) asmgen.asmVariableName(second.addressExpression as IdentifierReference) else null
@ -952,10 +782,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(pointerVariable != null
&& pointerVariable isSameAs secondExpr.left
&& firstExpr.operator == "+" && secondExpr.operator == "+"
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
&& (firstOffset is NumericLiteral || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
&& (secondOffset is NumericLiteral || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
) {
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) {
if(firstOffset is NumericLiteral && secondOffset is NumericLiteral) {
if(firstOffset!=secondOffset) {
swapArrayValues(
DataType.UBYTE,
@ -992,13 +822,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val arrayVarName1 = asmgen.asmVariableName(first.arrayvar)
val arrayVarName2 = asmgen.asmVariableName(second.arrayvar)
val elementIDt = first.inferType(program)
if(!elementIDt.isKnown)
throw AssemblyError("unknown dt")
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
val elementDt = elementIDt.getOrElse { throw AssemblyError("unknown dt") }
val firstNum = first.indexer.indexExpr as? NumericLiteralValue
val firstNum = first.indexer.indexExpr as? NumericLiteral
val firstVar = first.indexer.indexExpr as? IdentifierReference
val secondNum = second.indexer.indexExpr as? NumericLiteralValue
val secondNum = second.indexer.indexExpr as? NumericLiteral
val secondVar = second.indexer.indexExpr as? IdentifierReference
if(firstNum!=null && secondNum!=null) {
@ -1067,7 +895,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteral, arrayVarName2: String, indexValue2: NumericLiteral) {
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
when(elementDt) {
@ -1181,7 +1009,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) {
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteral, arrayVarName2: String, indexName2: IdentifierReference) {
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) {
@ -1239,7 +1067,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) {
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteral) {
val idxAsmName1 = asmgen.asmVariableName(indexName1)
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
when(elementDt) {
@ -1302,27 +1130,21 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val dt = fcall.args.single().inferType(program).getOr(DataType.UNDEFINED)
if(resultToStack) {
when (dt) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_stack")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w_stack")
DataType.FLOAT -> asmgen.out(" jsr floats.abs_f_stack")
DataType.UBYTE -> asmgen.out(" ldy #0")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.abs_b_stack")
DataType.UWORD -> {}
DataType.WORD -> asmgen.out(" jsr prog8_lib.abs_w_stack")
else -> throw AssemblyError("weird type")
}
} else {
when (dt) {
in ByteDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, program, asmgen), CpuRegister.A)
}
in WordDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.FLOAT -> {
asmgen.out(" jsr floats.abs_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, true, scope, program, asmgen))
}
DataType.UBYTE -> asmgen.out(" ldy #0")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.abs_b_into_AY")
DataType.UWORD -> {}
DataType.WORD -> asmgen.out(" jsr prog8_lib.abs_w_into_AY")
else -> throw AssemblyError("weird type")
}
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, scope, program, asmgen), RegisterOrPair.AY)
}
}
@ -1350,7 +1172,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcPokeW(fcall: IFunctionCall) {
when(val addrExpr = fcall.args[0]) {
is NumericLiteralValue -> {
is NumericLiteral -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
val addr = addrExpr.number.toHex()
asmgen.out(" sta $addr | sty ${addr}+1")
@ -1381,13 +1203,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
// pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
val index = (addrExpr.right as NumericLiteral).number.toHex()
asmgen.out("""
ldy #$index
sta ($varname),y
@ -1409,7 +1231,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcPeekW(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
when(val addrExpr = fcall.args[0]) {
is NumericLiteralValue -> {
is NumericLiteral -> {
val addr = addrExpr.number.toHex()
asmgen.out(" lda $addr | ldy ${addr}+1")
}
@ -1439,11 +1261,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
// pointervar is already in the zero page, no need to copy
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
val index = (addrExpr.right as NumericLiteral).number.toHex()
asmgen.out("""
ldy #$index
lda ($varname),y
@ -1490,14 +1312,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
} else {
val reg = resultRegister ?: RegisterOrPair.AY
var needAsave = !(fcall.args[0] is DirectMemoryRead || fcall.args[0] is NumericLiteralValue || fcall.args[0] is IdentifierReference)
var needAsave = !(fcall.args[0] is DirectMemoryRead || fcall.args[0] is NumericLiteral || fcall.args[0] is IdentifierReference)
if(!needAsave) {
val mr0 = fcall.args[0] as? DirectMemoryRead
val mr1 = fcall.args[1] as? DirectMemoryRead
if (mr0 != null)
needAsave = mr0.addressExpression !is NumericLiteralValue && mr0.addressExpression !is IdentifierReference
needAsave = mr0.addressExpression !is NumericLiteral && mr0.addressExpression !is IdentifierReference
if (mr1 != null)
needAsave = needAsave or (mr1.addressExpression !is NumericLiteralValue && mr1.addressExpression !is IdentifierReference)
needAsave = needAsave or (mr1.addressExpression !is NumericLiteral && mr1.addressExpression !is IdentifierReference)
}
when(reg) {
RegisterOrPair.AX -> {
@ -1540,7 +1362,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val arg = fcall.args.single()
if (!arg.inferType(program).isWords)
throw AssemblyError("msb required word argument")
if (arg is NumericLiteralValue)
if (arg is NumericLiteral)
throw AssemblyError("msb(const) should have been const-folded away")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg)
@ -1551,6 +1373,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+1 | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+1 | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+1 | ldy #0")
in Cx16VirtualRegisters -> {
val regname = resultRegister.name.lowercase()
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | stz cx16.$regname+1")
else
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
}
else -> throw AssemblyError("invalid reg")
}
}
@ -1584,7 +1416,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val arg = fcall.args.single()
if (!arg.inferType(program).isWords)
throw AssemblyError("lsb required word argument")
if (arg is NumericLiteralValue)
if (arg is NumericLiteral)
throw AssemblyError("lsb(const) should have been const-folded away")
if (arg is IdentifierReference) {
@ -1596,6 +1428,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName")
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
in Cx16VirtualRegisters -> {
val regname = resultRegister.name.lowercase()
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceName | sta cx16.$regname | stz cx16.$regname+1")
else
asmgen.out(" lda $sourceName | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
}
else -> throw AssemblyError("invalid reg")
}
}
@ -1634,8 +1476,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun outputAddressAndLenghtOfArray(arg: Expression) {
// address in P8ZP_SCRATCH_W1, number of elements in A
arg as IdentifierReference
val arrayVar = arg.targetVarDecl(program)!!
if(!arrayVar.isArray)
throw AssemblyError("length of non-array requested")
val size = arrayVar.arraysize!!.constIndex()!!
val identifierName = asmgen.asmVariableName(arg)
val size = arg.targetVarDecl(program)!!.arraysize!!.constIndex()!!
asmgen.out("""
lda #<$identifierName
ldy #>$identifierName
@ -1646,7 +1491,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
val callConv = signature.callConvention(args.map { it.inferType(program).getOr(DataType.UNDEFINED) })
val callConv = signature.callConvention(args.map {
it.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
})
fun getSourceForFloat(value: Expression): AsmAssignSource {
return when (value) {
@ -1654,14 +1501,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val addr = AddressOf(value, value.position)
AsmAssignSource.fromAstSource(addr, program, asmgen)
}
is NumericLiteralValue -> {
is NumericLiteral -> {
throw AssemblyError("float literals should have been converted into autovar")
}
else -> {
if(scope==null)
throw AssemblyError("cannot use float arguments outside of a subroutine scope")
scope.asmGenInfo.usedFloatEvalResultVar2 = true
asmgen.subroutineExtra(scope).usedFloatEvalResultVar2 = true
val variable = IdentifierReference(listOf(subroutineFloatEvalResultVar2), value.position)
val addr = AddressOf(variable, value.position)
addr.linkParents(value)

View File

@ -1,17 +1,13 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.cpu6502
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import prog8.code.core.*
import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal class ExpressionsAsmGen(private val program: Program,
private val asmgen: AsmGen,
private val allocator: VariableAllocator) {
@Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression:Expression) {
@ -34,108 +30,107 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is TypecastExpression -> translateExpression(expression)
is AddressOf -> translateExpression(expression)
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
is NumericLiteralValue -> translateExpression(expression)
is NumericLiteral -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCall -> translateFunctionCallResultOntoStack(expression)
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
is BuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
is PipeExpression -> asmgen.translatePipeExpression(expression.source, expression.segments,
expression, isStatement = false, pushResultOnEstack = true )
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
is ArrayLiteral, is StringLiteral -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpression -> throw AssemblyError("range expression should have been changed into array values")
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
else -> TODO("missing expression asmgen for $expression")
}
}
private fun translateFunctionCallResultOntoStack(call: FunctionCall) {
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpression) {
// only for use in nested expression evaluation
val sub = call.target.targetStatement(program)
if(sub is BuiltinFunctionStatementPlaceholder) {
val builtinFunc = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(call, builtinFunc, true, null)
} else {
sub as Subroutine
asmgen.saveXbeforeCall(call)
asmgen.translateFunctionCall(call, true)
if(sub.regXasResult()) {
// store the return value in X somewhere that we can acces again below
asmgen.out(" stx P8ZP_SCRATCH_REG")
}
asmgen.restoreXafterCall(call)
val sub = call.target.targetSubroutine(program)!!
asmgen.saveXbeforeCall(call)
asmgen.translateFunctionCall(call, true)
if(sub.regXasResult()) {
// store the return value in X somewhere that we can access again below
asmgen.out(" stx P8ZP_SCRATCH_REG")
}
asmgen.restoreXafterCall(call)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
if (reg.registerOrPair != null) {
when (reg.registerOrPair!!) {
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
RegisterOrPair.R0,
RegisterOrPair.R1,
RegisterOrPair.R2,
RegisterOrPair.R3,
RegisterOrPair.R4,
RegisterOrPair.R5,
RegisterOrPair.R6,
RegisterOrPair.R7,
RegisterOrPair.R8,
RegisterOrPair.R9,
RegisterOrPair.R10,
RegisterOrPair.R11,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
asmgen.out(
"""
lda cx16.${reg.registerOrPair.toString().lowercase()}
sta P8ESTACK_LO,x
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
sta P8ESTACK_HI,x
dex
""")
}
}
} else when(reg.statusflag) {
Statusflag.Pc -> {
asmgen.out("""
lda #0
rol a
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
if (reg.registerOrPair != null) {
when (reg.registerOrPair!!) {
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
RegisterOrPair.R0,
RegisterOrPair.R1,
RegisterOrPair.R2,
RegisterOrPair.R3,
RegisterOrPair.R4,
RegisterOrPair.R5,
RegisterOrPair.R6,
RegisterOrPair.R7,
RegisterOrPair.R8,
RegisterOrPair.R9,
RegisterOrPair.R10,
RegisterOrPair.R11,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
asmgen.out(
"""
lda cx16.${reg.registerOrPair.toString().lowercase()}
sta P8ESTACK_LO,x
dex""")
lda cx16.${reg.registerOrPair.toString().lowercase()}+1
sta P8ESTACK_HI,x
dex
""")
}
Statusflag.Pz -> {
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pv -> {
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pn -> {
asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
null -> {}
}
} else when(reg.statusflag) {
Statusflag.Pc -> {
asmgen.out("""
lda #0
rol a
sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pz -> {
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pv -> {
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
Statusflag.Pn -> {
asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+ sta P8ESTACK_LO,x
dex""")
}
null -> {}
}
}
}
@ -209,7 +204,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
}
private fun translateExpression(expr: NumericLiteralValue) {
private fun translateExpression(expr: NumericLiteral) {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
DataType.UWORD, DataType.WORD -> asmgen.out("""
@ -220,7 +215,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
dex
""")
DataType.FLOAT -> {
val floatConst = asmgen.getFloatAsmConst(expr.number)
val floatConst = allocator.getFloatAsmConst(expr.number)
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
}
else -> throw AssemblyError("weird type")
@ -254,8 +249,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(!leftIDt.isKnown || !rightIDt.isKnown)
throw AssemblyError("can't infer type of both expression operands")
val leftDt = leftIDt.getOr(DataType.UNDEFINED)
val rightDt = rightIDt.getOr(DataType.UNDEFINED)
val leftDt = leftIDt.getOrElse { throw AssemblyError("unknown dt") }
val rightDt = rightIDt.getOrElse { throw AssemblyError("unknown dt") }
// see if we can apply some optimized routines
when(expr.operator) {
"+" -> {
@ -420,7 +415,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
left -= 7
}
if (left in 0..2)
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
repeat(left) { asmgen.out(" lda P8ESTACK_HI+1,x | asl a | ror P8ESTACK_HI+1,x | ror P8ESTACK_LO+1,x") }
else
asmgen.out(" jsr math.shift_right_w_$left")
}
@ -537,13 +532,20 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
}
in ComparisonOperators -> {
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if(rightVal==0)
return translateComparisonWithZero(expr.left, leftDt, expr.operator)
}
}
}
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in comparisonOperators) {
if(leftDt==DataType.STR && rightDt==DataType.STR && expr.operator in ComparisonOperators) {
translateCompareStrings(expr.left, expr.operator, expr.right)
}
else {
@ -559,6 +561,69 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
}
private fun translateComparisonWithZero(expr: Expression, dt: DataType, operator: String) {
translateExpressionInternal(expr)
when(operator) {
"==" -> {
when(dt) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
DataType.FLOAT -> asmgen.out(" jsr floats.equal_zero")
else -> throw AssemblyError("wrong dt")
}
}
"!=" -> {
when(dt) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.notequalzero_b")
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.notequalzero_w")
DataType.FLOAT -> asmgen.out(" jsr floats.notequal_zero")
else -> throw AssemblyError("wrong dt")
}
}
"<" -> {
if(dt==DataType.UBYTE || dt==DataType.UWORD)
return translateExpressionInternal(NumericLiteral.fromBoolean(false, expr.position))
when(dt) {
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
DataType.FLOAT -> asmgen.out(" jsr floats.less_zero")
else -> throw AssemblyError("wrong dt")
}
}
">" -> {
when(dt) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.greaterzero_ub")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterzero_sb")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.greaterzero_uw")
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterzero_sw")
DataType.FLOAT -> asmgen.out(" jsr floats.greater_zero")
else -> throw AssemblyError("wrong dt")
}
}
"<=" -> {
when(dt) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lessequalzeros_b")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
DataType.WORD -> asmgen.out(" jsr prog8_lib.lessequalzero_sw")
DataType.FLOAT -> asmgen.out(" jsr floats.lessequal_zero")
else -> throw AssemblyError("wrong dt")
}
}
">=" -> {
if(dt==DataType.UBYTE || dt==DataType.UWORD)
return translateExpressionInternal(NumericLiteral.fromBoolean(true, expr.position))
when(dt) {
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
DataType.FLOAT -> asmgen.out(" jsr floats.greaterequal_zero")
else -> throw AssemblyError("wrong dt")
}
}
else -> throw AssemblyError("invalid comparison operator")
}
}
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
val asmVar = asmgen.asmVariableName(variable)
when(dt) {
@ -578,9 +643,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateExpression(expr: PrefixExpression) {
translateExpressionInternal(expr.expression)
val itype = expr.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.getOr(DataType.UNDEFINED)
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
when(expr.operator) {
"+" -> {}
"-" -> {
@ -605,6 +668,15 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
"not" -> {
when(type) {
// if reg==0 ->
/*
lda P8ESTACK_LO+1,x
beq +
lda #1
+ eor #1
sta P8ESTACK_LO+1,x
rts
*/
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
else -> throw AssemblyError("weird type")
@ -631,7 +703,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float")
asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue) | jsr floats.push_float")
}
else -> throw AssemblyError("weird element type")
}
@ -663,7 +735,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"%" -> {
@ -705,7 +776,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateBinaryOperatorWords(operator: String, dt: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
"/" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
"%" -> {
@ -727,7 +797,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
"<=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
">=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w") "&" -> asmgen.out(" jsr prog8_lib.bitand_w")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w")
"&" -> asmgen.out(" jsr prog8_lib.bitand_w")
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
"and" -> asmgen.out(" jsr prog8_lib.and_w")
@ -739,7 +810,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateBinaryOperatorFloats(operator: String) {
when(operator) {
"**" -> asmgen.out(" jsr floats.pow_f")
"*" -> asmgen.out(" jsr floats.mul_f")
"/" -> asmgen.out(" jsr floats.div_f")
"+" -> asmgen.out(" jsr floats.add_f")

View File

@ -1,40 +1,36 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import prog8.ast.Program
import prog8.ast.base.ArrayToElementTypes
import prog8.ast.base.DataType
import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr
import prog8.ast.expressions.RangeExpression
import prog8.ast.statements.ForLoop
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compilerinterface.toConstantIntegerRange
import prog8.code.core.*
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen, private val zeropage: Zeropage) {
internal fun translate(stmt: ForLoop) {
val iterableDt = stmt.iterable.inferType(program)
if(!iterableDt.isKnown)
throw AssemblyError("unknown dt")
when(stmt.iterable) {
is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
is RangeExpression -> {
val range = (stmt.iterable as RangeExpression).toConstantIntegerRange()
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as RangeExpr)
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpression)
} else {
translateForOverConstRange(stmt, iterableDt.getOr(DataType.UNDEFINED), range)
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
}
}
is IdentifierReference -> {
translateForOverIterableVar(stmt, iterableDt.getOr(DataType.UNDEFINED), stmt.iterable as IdentifierReference)
translateForOverIterableVar(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as IdentifierReference)
}
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
}
}
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) {
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpression) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val modifiedLabel = asmgen.makeLabel("for_modified")
@ -289,13 +285,15 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
if(length>=16) {
// allocate index var on ZP if possible
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("""
$indexVar .byte 0""")
asmgen.out("$indexVar .byte 0")
}
asmgen.out(endLabel)
}
@ -328,13 +326,15 @@ $loopLabel sty $indexVar
bne $loopLabel
beq $endLabel""")
}
if(length>=16 && asmgen.zeropage.hasByteAvailable()) {
// allocate index var on ZP
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""")
if(length>=16) {
// allocate index var on ZP if possible
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
result.fold(
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else {
asmgen.out("""
$indexVar .byte 0""")
asmgen.out("$indexVar .byte 0")
}
asmgen.out(endLabel)
}
@ -588,6 +588,10 @@ $loopLabel""")
asmgen.loopEndLabels.pop()
}
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).getOr(DataType.UNDEFINED), stmt.definingSubroutine)
private fun assignLoopvar(stmt: ForLoop, range: RangeExpression) =
asmgen.assignExpressionToVariable(
range.from,
asmgen.asmVariableName(stmt.loopVar),
stmt.loopVarDt(program).getOrElse { throw AssemblyError("unknown dt") },
stmt.definingSubroutine)
}

View File

@ -1,22 +1,23 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.cpu6502
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.*
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
import prog8.compilerinterface.CpuType
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignSource
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.cpu6502.assignment.AsmAssignment
import prog8.codegen.cpu6502.assignment.TargetStorageKind
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
internal fun translateFunctionCallStatement(stmt: FunctionCallStatement) {
saveXbeforeCall(stmt)
translateFunctionCall(stmt, false)
restoreXafterCall(stmt)
@ -35,7 +36,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
internal fun saveXbeforeCall(gosub: GoSub) {
val sub = gosub.identifier?.targetSubroutine(program)
val sub = gosub.identifier.targetSubroutine(program)
if(sub?.shouldSaveX()==true) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
@ -57,7 +58,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
internal fun restoreXafterCall(gosub: GoSub) {
val sub = gosub.identifier?.targetSubroutine(program)
val sub = gosub.identifier.targetSubroutine(program)
if(sub?.shouldSaveX()==true) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
if(regSaveOnStack)
@ -67,6 +68,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) {
// Output only the code to set up the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values!
@ -76,8 +81,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
val sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
val subAsmName = asmgen.asmSymbolName(call.target)
if(!isExpression && !sub.isAsmSubroutine)
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
if(!isExpression && !sub.isAsmSubroutine) {
if(!optimizeIntArgsViaRegisters(sub))
throw AssemblyError("functioncall statements to non-asmsub should have been replaced by GoSub $call")
}
if(sub.isAsmSubroutine) {
argumentsViaRegisters(sub, call)
@ -87,8 +94,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
// (this condition has been enforced by an ast check earlier)
asmgen.out(" \t; inlined routine follows: ${sub.name}")
val assembly = sub.statements.single() as InlineAssembly
asmgen.translate(assembly)
sub.statements.forEach { asmgen.translate(it as InlineAssembly) }
asmgen.out(" \t; inlined routine end: ${sub.name}")
} else {
asmgen.out(" jsr $subAsmName")
@ -98,26 +104,38 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(sub.inline)
throw AssemblyError("can only reliably inline asmsub routines at this time")
argumentsViaVariables(sub, call)
if(optimizeIntArgsViaRegisters(sub)) {
if(sub.parameters.size==1) {
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], register)
} else {
// 2 byte params, second in Y, first in A
argumentViaRegister(sub, IndexedValue(0, sub.parameters[0]), call.args[0], RegisterOrPair.A)
if(!call.args[1].isSimple)
asmgen.out(" pha")
argumentViaRegister(sub, IndexedValue(1, sub.parameters[1]), call.args[1], RegisterOrPair.Y)
if(!call.args[1].isSimple)
asmgen.out(" pla")
}
} else {
// arguments via variables
for(arg in sub.parameters.withIndex().zip(call.args))
argumentViaVariable(sub, arg.first.value, arg.second)
}
asmgen.out(" jsr $subAsmName")
}
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) {
for(arg in sub.parameters.withIndex().zip(call.args))
argumentViaVariable(sub, arg.first, arg.second)
}
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
if(sub.parameters.size==1) {
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
} else {
if(asmgen.asmsubArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
registerArgsViaStackEvaluation(call, sub)
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
registerArgsViaCpuStackEvaluation(call, sub)
} else {
asmgen.asmsubArgsEvalOrder(sub).forEach {
asmsub6502ArgsEvalOrder(sub).forEach {
val param = sub.parameters[it]
val arg = call.args[it]
argumentViaRegister(sub, IndexedValue(it, param), arg)
@ -126,154 +144,45 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
private fun registerArgsViaCpuStackEvaluation(call: IFunctionCall, callee: 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.
// TODO find another way to prepare the arguments, without using the eval stack
if(sub.parameters.isEmpty())
require(callee.isAsmSubroutine)
if(callee.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)
// TODO here's an alternative to the above, but for now generates bigger code due to intermediate register steps:
// for (arg in stmt.args.reversed()) {
// // note this stuff below is needed to (eventually) avoid calling asmgen.translateExpression()
// // TODO also This STILL requires the translateNormalAssignment() to be fixed to avoid stack eval for expressions...
// val dt = arg.inferType(program).getOr(DataType.UNDEFINED)
// asmgen.assignExpressionTo(arg, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, dt, sub))
// }
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()) {
val plusIdxStr = if(argi.index==0) "" else "+${argi.index}"
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 arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
require(argForXregister==null)
argForXregister = argi
}
argi.value.second.registerOrPair in arrayOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
require(argForAregister == null)
argForAregister = argi
}
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
asmgen.out(" ldy P8ESTACK_LO$plusIdxStr,x")
}
argi.value.second.registerOrPair in Cx16VirtualRegisters -> {
// immediately output code to load the virtual register, to avoid clobbering the A register later
when (sub.parameters[argi.index].type) {
in ByteDatatypes -> {
// only load the lsb of the virtual register
asmgen.out(
"""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
""")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(
" stz cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1")
else
asmgen.out(
" lda #0 | sta cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1")
}
in WordDatatypes, in IterableDatatypes ->
asmgen.out(
"""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().lowercase()}
lda P8ESTACK_HI$plusIdxStr,x
sta cx16.${
argi.value.second.registerOrPair.toString().lowercase()
}+1
""")
else -> throw AssemblyError("weird dt")
}
}
else -> throw AssemblyError("weird argument")
}
// use the cpu hardware stack as intermediate storage for the arguments.
val argOrder = asmsub6502ArgsEvalOrder(callee)
argOrder.reversed().forEach {
asmgen.pushCpuStack(callee.parameters[it].type, call.args[it])
}
if(argForCarry!=null) {
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
asmgen.out("""
clc
lda P8ESTACK_LO$plusIdxStr,x
beq +
sec
+ php""") // push the status flags
argOrder.forEach {
val param = callee.parameters[it]
val targetVar = callee.searchAsmParameter(param.name)!!
asmgen.popCpuStack(param.type, targetVar, (call as Node).definingSubroutine)
}
if(argForAregister!=null) {
val plusIdxStr = if(argForAregister.index==0) "" else "+${argForAregister.index}"
when(argForAregister.value.second.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x")
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x | ldy P8ESTACK_HI$plusIdxStr,x")
else -> throw AssemblyError("weird arg")
}
}
if(argForXregister!=null) {
val plusIdxStr = if(argForXregister.index==0) "" else "+${argForXregister.index}"
if(argForAregister!=null)
asmgen.out(" pha")
when(argForXregister.value.second.registerOrPair) {
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO$plusIdxStr,x | tax")
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO$plusIdxStr,x | lda P8ESTACK_HI$plusIdxStr,x | tax | tya")
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI$plusIdxStr,x | lda P8ESTACK_LO$plusIdxStr,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) {
private fun argumentViaVariable(sub: Subroutine, parameter: 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.getOr(DataType.UNDEFINED)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.type))
throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedName + parameter.value.name)
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
val varName = asmgen.asmVariableName(sub.scopedName + parameter.name)
asmgen.assignExpressionToVariable(value, varName, parameter.type, sub)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val paramRegister = if(registerOverride==null) sub.asmParameterRegisters[parameter.index] else RegisterOrStatusflag(registerOverride, null)
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val requiredDt = parameter.value.type
@ -288,29 +197,28 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
is NumericLiteral -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
clc
lda $sourceName
beq +
sec
+ pla
""")
pha
clc
lda $sourceName
beq +
sec
+ pla""")
}
else -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
beq +
sec
bcs ++
+ clc
+""")
beq +
sec
bcs ++
+ clc
+""")
}
}
} else throw AssemblyError("can only use Carry as status flag parameter")

View File

@ -1,12 +1,10 @@
package prog8.compiler.target.cpu6502.codegen
package prog8.codegen.cpu6502
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.code.core.*
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -41,7 +39,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
targetMemory!=null -> {
when (val addressExpr = targetMemory.addressExpression) {
is NumericLiteralValue -> {
is NumericLiteral -> {
val what = addressExpr.number.toHex()
asmgen.out(if(incr) " inc $what" else " dec $what")
}
@ -83,7 +81,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
""")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue")
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")

View File

@ -0,0 +1,656 @@
package prog8.codegen.cpu6502
import prog8.ast.Program
import prog8.ast.statements.*
import prog8.code.*
import prog8.code.core.*
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.cpu6502.assignment.TargetStorageKind
import prog8.compiler.CallGraph
import java.time.LocalDate
import java.time.LocalDateTime
import kotlin.math.absoluteValue
/**
* Generates the main parts of the program:
* - entry/exit code
* - initialization routines
* - blocks
* - subroutines
* - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
*/
internal class ProgramAndVarsGen(
val program: Program,
val options: CompilationOptions,
val errors: IErrorReporter,
private val symboltable: SymbolTable,
private val functioncallAsmGen: FunctionCallAsmGen,
private val asmgen: AsmGen,
private val allocator: VariableAllocator,
private val zeropage: Zeropage
) {
private val compTarget = options.compTarget
private val callGraph = CallGraph(program, true)
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
internal fun generate() {
val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value }
require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"}
header()
val allBlocks = program.allBlocks
if(allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main'")
if(errors.noErrors()) {
program.allBlocks.forEach { block2asm(it) }
memorySlabs()
footer()
}
}
private fun header() {
val ourName = this.javaClass.name
val cpu = when(compTarget.machine.cpu) {
CpuType.CPU6502 -> "6502"
CpuType.CPU65c02 -> "w65c02"
else -> "unsupported"
}
asmgen.out("; $cpu assembly code for '${program.name}'")
asmgen.out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}")
asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
asmgen.out("")
asmgen.out(".cpu '$cpu'\n.enc 'none'\n")
// the global prog8 variables needed
val zp = zeropage
asmgen.out("P8ZP_SCRATCH_B1 = ${zp.SCRATCH_B1}")
asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
when(options.output) {
OutputType.RAW -> {
asmgen.out("; ---- raw assembler program ----")
asmgen.out("* = ${options.loadAddress.toHex()}\n")
}
OutputType.PRG -> {
when(options.launcher) {
CbmPrgLauncherType.BASIC -> {
if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.toplevelModule.position)
}
asmgen.out("; ---- basic program with sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}")
val year = LocalDate.now().year
asmgen.out(" .word (+), $year")
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
asmgen.out("+\t.word 0")
asmgen.out("prog8_entrypoint\t; assembly code starts here\n")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
CbmPrgLauncherType.NONE -> {
asmgen.out("; ---- program without basic sys call ----")
asmgen.out("* = ${options.loadAddress.toHex()}\n")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
}
}
OutputType.XEX -> {
asmgen.out("; ---- atari xex program ----")
asmgen.out("* = ${options.loadAddress.toHex()}\n")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
}
if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
asmgen.out("""
; zeropage is clobbered so we need to reset the machine at exit
lda #>sys.reset_system
pha
lda #<sys.reset_system
pha""")
}
// make sure that on the cx16 and c64, basic rom is banked in again when we exit the program
when(compTarget.name) {
"cx16" -> {
if(options.floats)
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
asmgen.out(" jsr main.start | lda #4 | sta $01")
if(!options.noSysInit)
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
else
asmgen.out(" rts")
}
"c64" -> {
asmgen.out(" jsr main.start | lda #31 | sta $01")
if(!options.noSysInit)
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
else
asmgen.out(" rts")
}
"c128" -> {
asmgen.out(" jsr main.start")
// TODO c128: how to bank basic+kernal back in?
if(!options.noSysInit)
asmgen.out(" jmp ${compTarget.name}.cleanup_at_exit")
else
asmgen.out(" rts")
}
else -> asmgen.jmp("main.start")
}
}
private fun memorySlabs() {
asmgen.out("; memory slabs")
asmgen.out("prog8_slabs\t.block")
for((name, info) in allocator.memorySlabs) {
if(info.second>1u)
asmgen.out("\t.align ${info.second.toHex()}")
asmgen.out("$name\t.fill ${info.first}")
}
asmgen.out("\t.bend")
}
private fun footer() {
// the global list of all floating point constants for the whole program
asmgen.out("; global float constants")
for (flt in allocator.globalFloatConsts) {
val floatFill = compTarget.machine.getFloat(flt.key).makeFloatFillAsm()
val floatvalue = flt.key
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
}
// program end
asmgen.out("prog8_program_end\t; end of program label for progend()")
}
private fun block2asm(block: Block) {
asmgen.out("")
asmgen.out("; ---- block: '${block.name}' ----")
if(block.address!=null)
asmgen.out("* = ${block.address!!.toHex()}")
else {
if("align_word" in block.options())
asmgen.out("\t.align 2")
else if("align_page" in block.options())
asmgen.out("\t.align $100")
}
asmgen.out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
asmgen.outputSourceLine(block)
createBlockVariables(block)
asmsubs2asm(block.statements)
asmgen.out("")
asmgen.out("; subroutines in this block")
// First translate regular statements, and then put the subroutines at the end.
// (regular statements = everything except the initialization assignments;
// these will be part of the prog8_init_vars init routine generated below)
val initializers = blockVariableInitializers.getValue(block)
val statements = block.statements.filterNot { it in initializers }
val (subroutine, stmts) = statements.partition { it is Subroutine }
stmts.forEach { asmgen.translate(it) }
subroutine.forEach { asmgen.translate(it) }
if(!options.dontReinitGlobals) {
// generate subroutine to initialize block-level (global) variables
if (initializers.isNotEmpty()) {
asmgen.out("prog8_init_vars\t.proc\n")
initializers.forEach { assign -> asmgen.translate(assign) }
asmgen.out(" rts\n .pend")
}
}
asmgen.out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
}
private fun getVars(scope: StNode): Map<String, StNode> =
scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }
private fun createBlockVariables(block: Block) {
val scope = symboltable.lookupOrElse(block.name) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.BLOCK)
val varsInBlock = getVars(scope)
// Zeropage Variables
val varnames = varsInBlock.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
zeropagevars2asm(varnames)
// MemDefs and Consts
val mvs = varsInBlock
.filter { it.value.type==StNodeType.MEMVAR }
.map { it.value as StMemVar }
val consts = varsInBlock
.filter { it.value.type==StNodeType.CONSTANT }
.map { it.value as StConstant }
memdefsAndConsts2asm(mvs, consts)
// normal statically allocated variables
val variables = varsInBlock
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
}
internal fun translateSubroutine(sub: Subroutine) {
var onlyVariables = false
if(sub.inline) {
if(options.optimize) {
if(sub.isAsmSubroutine || callGraph.unused(sub))
return
// from an inlined subroutine only the local variables are generated,
// all other code statements are omitted in the subroutine itself
// (they've been inlined at the call site, remember?)
onlyVariables = true
}
}
asmgen.out("")
if(sub.isAsmSubroutine) {
if(sub.asmAddress!=null)
return // already done at the memvars section
// asmsub with most likely just an inline asm in it
asmgen.out("${sub.name}\t.proc")
sub.statements.forEach { asmgen.translate(it) }
asmgen.out(" .pend\n")
} else {
// regular subroutine
asmgen.out("${sub.name}\t.proc")
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
require(scope.type==StNodeType.SUBROUTINE)
val varsInSubroutine = getVars(scope)
// Zeropage Variables
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
zeropagevars2asm(varnames)
// MemDefs and Consts
val mvs = varsInSubroutine
.filter { it.value.type==StNodeType.MEMVAR }
.map { it.value as StMemVar }
val consts = varsInSubroutine
.filter { it.value.type==StNodeType.CONSTANT }
.map { it.value as StConstant }
memdefsAndConsts2asm(mvs, consts)
asmsubs2asm(sub.statements)
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
if(sub.name=="start" && sub.definingBlock.name=="main")
entrypointInitialization()
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
asmgen.out("; simple int arg(s) passed via register(s)")
if(sub.parameters.size==1) {
val dt = sub.parameters[0].type
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
if(dt in ByteDatatypes)
asmgen.assignRegister(RegisterOrPair.A, target)
else
asmgen.assignRegister(RegisterOrPair.AY, target)
} else {
require(sub.parameters.size==2)
// 2 simple byte args, first in A, second in Y
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
asmgen.assignRegister(RegisterOrPair.A, target1)
asmgen.assignRegister(RegisterOrPair.Y, target2)
}
}
if(!onlyVariables) {
asmgen.out("; statements")
sub.statements.forEach { asmgen.translate(it) }
}
asmgen.out("; variables")
val asmGenInfo = asmgen.subroutineExtra(sub)
for((dt, name, addr) in asmGenInfo.extraVars) {
if(addr!=null)
asmgen.out("$name = $addr")
else when(dt) {
DataType.UBYTE -> asmgen.out("$name .byte 0")
DataType.UWORD -> asmgen.out("$name .word 0")
else -> throw AssemblyError("weird dt")
}
}
if(asmGenInfo.usedRegsaveA) // will probably never occur
asmgen.out("prog8_regsaveA .byte 0")
if(asmGenInfo.usedRegsaveX)
asmgen.out("prog8_regsaveX .byte 0")
if(asmGenInfo.usedRegsaveY)
asmgen.out("prog8_regsaveY .byte 0")
if(asmGenInfo.usedFloatEvalResultVar1)
asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
if(asmGenInfo.usedFloatEvalResultVar2)
asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
// normal statically allocated variables
val variables = varsInSubroutine
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
.map { it.value as StStaticVariable }
nonZpVariables2asm(variables)
asmgen.out(" .pend\n")
}
}
private fun entrypointInitialization() {
asmgen.out("; program startup initialization")
asmgen.out(" cld")
if(!options.dontReinitGlobals) {
blockVariableInitializers.forEach {
if (it.value.isNotEmpty())
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
}
}
// string and array variables in zeropage that have initializer value, should be initialized
val stringVarsWithInitInZp = getZpStringVarsWithInitvalue()
val arrayVarsWithInitInZp = getZpArrayVarsWithInitvalue()
if(stringVarsWithInitInZp.isNotEmpty() || arrayVarsWithInitInZp.isNotEmpty()) {
asmgen.out("; zp str and array initializations")
stringVarsWithInitInZp.forEach {
val name = asmgen.asmVariableName(it.name)
asmgen.out("""
lda #<${name}
ldy #>${name}
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${name}_init_value
ldy #>${name}_init_value
jsr prog8_lib.strcpy""")
}
arrayVarsWithInitInZp.forEach {
val size = it.alloc.size
val name = asmgen.asmVariableName(it.name)
asmgen.out("""
lda #<${name}_init_value
ldy #>${name}_init_value
sta cx16.r0L
sty cx16.r0H
lda #<${name}
ldy #>${name}
sta cx16.r1L
sty cx16.r1H
lda #<$size
ldy #>$size
jsr sys.memcopy""")
}
asmgen.out(" jmp +")
}
stringVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.name)+"_init_value"
outputStringvar(varname, it.value.second, it.value.first)
}
arrayVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.name)+"_init_value"
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
}
asmgen.out("""+ tsx
stx prog8_lib.orig_stackpointer ; required for sys.exit()
ldx #255 ; init estack ptr
clv
clc""")
}
private class ZpStringWithInitial(
val name: List<String>,
val alloc: Zeropage.ZpAllocation,
val value: Pair<String, Encoding>
)
private class ZpArrayWithInitial(
val name: List<String>,
val alloc: Zeropage.ZpAllocation,
val value: StArray
)
private fun getZpStringVarsWithInitvalue(): Collection<ZpStringWithInitial> {
val result = mutableListOf<ZpStringWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
for (variable in vars) {
val svar = symboltable.lookup(variable.key) as StStaticVariable // TODO faster in flat lookup table
if(svar.initialStringValue!=null)
result.add(ZpStringWithInitial(variable.key, variable.value, svar.initialStringValue!!))
}
return result
}
private fun getZpArrayVarsWithInitvalue(): Collection<ZpArrayWithInitial> {
val result = mutableListOf<ZpArrayWithInitial>()
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
for (variable in vars) {
val svar = symboltable.lookup(variable.key) as StStaticVariable // TODO faster in flat lookup table
if(svar.initialArrayValue!=null)
result.add(ZpArrayWithInitial(variable.key, variable.value, svar.initialArrayValue!!))
}
return result
}
private fun zeropagevars2asm(varNames: Set<List<String>>) {
val zpVariables = allocator.zeropageVars.filter { it.key in varNames }
for ((scopedName, zpvar) in zpVariables) {
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit())
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
asmgen.out("${scopedName.last()} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
}
}
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
asmgen.out("")
asmgen.out("; non-zeropage variables")
val (stringvars, othervars) = variables.partition { it.dt==DataType.STR }
stringvars.forEach {
outputStringvar(it.name, it.initialStringValue!!.second, it.initialStringValue!!.first)
}
othervars.sortedBy { it.type }.forEach {
staticVariable2asm(it)
}
}
private fun staticVariable2asm(variable: StStaticVariable) {
val name = variable.name
val initialValue: Number =
if(variable.initialNumericValue!=null) {
if(variable.dt== DataType.FLOAT)
variable.initialNumericValue!!
else
variable.initialNumericValue!!.toInt()
} else 0
when (variable.dt) {
DataType.UBYTE -> asmgen.out("$name\t.byte ${initialValue.toHex()}")
DataType.BYTE -> asmgen.out("$name\t.char $initialValue")
DataType.UWORD -> asmgen.out("$name\t.word ${initialValue.toHex()}")
DataType.WORD -> asmgen.out("$name\t.sint $initialValue")
DataType.FLOAT -> {
if(initialValue==0) {
asmgen.out("$name\t.byte 0,0,0,0,0 ; float")
} else {
val floatFill = compTarget.machine.getFloat(initialValue).makeFloatFillAsm()
asmgen.out("$name\t.byte $floatFill ; float $initialValue")
}
}
DataType.STR -> {
throw AssemblyError("all string vars should have been interned into prog")
}
in ArrayDatatypes -> arrayVariable2asm(name, variable.dt, variable.initialArrayValue, variable.length)
else -> {
throw AssemblyError("weird dt")
}
}
}
private fun arrayVariable2asm(varname: String, dt: DataType, value: StArray?, orNumberOfZeros: Int?) {
when(dt) {
DataType.ARRAY_UB -> {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.byte ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .byte " + chunk.joinToString())
}
}
DataType.ARRAY_B -> {
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.char ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .char " + chunk.joinToString())
}
}
DataType.ARRAY_UW -> {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.word ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .word " + chunk.joinToString())
}
}
DataType.ARRAY_W -> {
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.sint ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .sint " + chunk.joinToString())
}
}
DataType.ARRAY_F -> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
val floatFills = array.map {
compTarget.machine.getFloat(it.number!!).makeFloatFillAsm()
}
asmgen.out(varname)
for (f in array.zip(floatFills))
asmgen.out(" .byte ${f.second} ; float ${f.first}")
}
else -> throw AssemblyError("require array dt")
}
}
private fun zeroFilledArray(numElts: Int): StArray {
val values = mutableListOf<StArrayElement>()
repeat(numElts) {
values.add(StArrayElement(0.0, null))
}
return values
}
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
memvars.forEach {
asmgen.out(" ${it.name} = ${it.address.toHex()}")
}
consts.forEach {
if(it.dt==DataType.FLOAT)
asmgen.out(" ${it.name} = ${it.value}")
else
asmgen.out(" ${it.name} = ${it.value.toHex()}")
}
}
private fun asmsubs2asm(statements: List<Statement>) {
statements
.filter { it is Subroutine && it.isAsmSubroutine && it.asmAddress!=null }
.forEach { asmsub ->
asmsub as Subroutine
asmgen.out(" ${asmsub.name} = ${asmsub.asmAddress!!.toHex()}")
}
}
private fun outputStringvar(varname: String, encoding: Encoding, value: String) {
asmgen.out("$varname\t; $encoding:\"${value.escape().replace("\u0000", "<NULL>")}\"")
val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte())
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
for (chunk in outputBytes.chunked(16))
asmgen.out(" .byte " + chunk.joinToString())
}
private fun makeArrayFillDataUnsigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
return when (dt) {
DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = it.number!!.toInt()
"$"+number.toString(16).padStart(2, '0')
}
DataType.ARRAY_UW -> array.map {
if(it.number!=null) {
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
}
else if(it.addressOf!=null) {
asmgen.asmSymbolName(it.addressOf!!)
}
else
throw AssemblyError("weird array elt")
}
else -> throw AssemblyError("invalid dt")
}
}
private fun makeArrayFillDataSigned(dt: DataType, value: StArray?, orNumberOfZeros: Int?): List<String> {
val array = value ?: zeroFilledArray(orNumberOfZeros!!)
return when (dt) {
// byte array can never contain pointer-to types, so treat values as all integers
DataType.ARRAY_UB ->
array.map {
val number = it.number!!.toInt()
"$"+number.toString(16).padStart(2, '0')
}
DataType.ARRAY_B ->
array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
DataType.ARRAY_UW -> array.map {
val number = it.number!!.toInt()
"$" + number.toString(16).padStart(4, '0')
}
DataType.ARRAY_W -> array.map {
val number = it.number!!.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
else -> throw AssemblyError("invalid dt")
}
}
}

View File

@ -0,0 +1,137 @@
package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import com.github.michaelbull.result.onSuccess
import prog8.code.StNode
import prog8.code.StNodeType
import prog8.code.StStaticVariable
import prog8.code.SymbolTable
import prog8.code.core.*
internal class VariableAllocator(private val symboltable: SymbolTable,
private val options: CompilationOptions,
private val errors: IErrorReporter
) {
private val zeropage = options.compTarget.machine.zeropage
private val memorySlabsInternal = mutableMapOf<String, Pair<UInt, UInt>>()
internal val memorySlabs: Map<String, Pair<UInt, UInt>> = memorySlabsInternal
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
internal val zeropageVars: Map<List<String>, Zeropage.ZpAllocation> = zeropage.allocatedVariables
init {
allocateZeropageVariables()
}
internal fun getMemorySlab(name: String) = memorySlabsInternal[name]
internal fun allocateMemorySlab(name: String, size: UInt, align: UInt) {
memorySlabsInternal[name] = Pair(size, align)
}
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables
internal fun getFloatAsmConst(number: Double): String {
val asmName = globalFloatConsts[number]
if(asmName!=null)
return asmName
val newName = "prog8_float_const_${globalFloatConsts.size}"
globalFloatConsts[number] = newName
return newName
}
/**
* Allocate variables into the Zeropage.
* The result should be retrieved from the current machine's zeropage object!
*/
private fun allocateZeropageVariables() {
if(options.zeropage== ZeropageType.DONTUSE)
return
val allVariables = collectAllVariables(symboltable)
val numberOfAllocatableVariables = allVariables.size
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
val varsPreferringZp = allVariables.filter { it.zpwish == ZeropageWish.PREFER_ZEROPAGE }
val varsDontCare = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }
val numberOfExplicitNonZpVariables = allVariables.count { it.zpwish == ZeropageWish.NOT_IN_ZEROPAGE }
require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + numberOfExplicitNonZpVariables == numberOfAllocatableVariables)
var numVariablesAllocatedInZP = 0
var numberOfNonIntegerVariables = 0
varsRequiringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.dt,
variable.length,
variable.position,
errors
)
result.fold(
success = {
numVariablesAllocatedInZP++
},
failure = {
errors.err(it.message!!, variable.position)
}
)
}
if(errors.noErrors()) {
varsPreferringZp.forEach { variable ->
val result = zeropage.allocate(
variable.scopedName,
variable.dt,
variable.length,
variable.position,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
// no need to check for allocation error, if there is one, just allocate in normal system ram.
}
// try to allocate any other interger variables into the zeropage until it is full.
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
if(errors.noErrors()) {
for (variable in varsDontCare.sortedBy { it.scopedName.size }) {
if(variable.dt in IntegerDatatypes) {
if(zeropage.free.isEmpty()) {
break
} else {
val result = zeropage.allocate(
variable.scopedName,
variable.dt,
variable.length,
variable.position,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
}
} else
numberOfNonIntegerVariables++
}
}
}
println(" number of allocated vars: $numberOfAllocatableVariables")
println(" put into zeropage: $numVariablesAllocatedInZP, non-zp allocatable: ${numberOfNonIntegerVariables+numberOfExplicitNonZpVariables}")
println(" zeropage free space: ${zeropage.free.size} bytes")
}
private fun collectAllVariables(st: SymbolTable): Collection<StStaticVariable> {
val vars = mutableListOf<StStaticVariable>()
fun collect(node: StNode) {
for(child in node.children) {
if(child.value.type == StNodeType.STATICVAR)
vars.add(child.value as StStaticVariable)
else
collect(child.value)
}
}
collect(st)
return vars
}
}

View File

@ -1,12 +1,10 @@
package prog8.compiler.target.cpu6502.codegen.assignment
package prog8.codegen.cpu6502.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compilerinterface.IMemSizer
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen
internal enum class TargetStorageKind {
@ -59,9 +57,7 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
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.getOr(DataType.UNDEFINED)
val dt = idt.getOrElse { throw AssemblyError("unknown dt") }
when {
identifier != null -> {
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
@ -122,7 +118,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryRead? = null,
val register: RegisterOrPair? = null,
val number: NumericLiteralValue? = null,
val number: NumericLiteral? = null,
val expression: Expression? = null
)
{
@ -144,9 +140,9 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
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 NumericLiteral -> throw AssemblyError("should have been constant value")
is StringLiteral -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteral -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val parameter = value.targetVarDecl(program)?.subroutineParameter
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
@ -166,33 +162,23 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
}
is ArrayIndexedExpression -> {
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
}
is FunctionCall -> {
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
?: throw AssemblyError("can't translate zero return values in assignment")
is BuiltinFunctionCall -> {
val returnType = value.inferType(program)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
}
is FunctionCallExpression -> {
val sub = value.target.targetSubroutine(program)!!
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=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.getOr(DataType.UNDEFINED), expression = value)
}
else -> {
throw AssemblyError("weird call")
}
}
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
}
else -> {
val dt = value.inferType(program)
if(!dt.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.getOr(DataType.UNDEFINED), expression = value)
val returnType = value.inferType(program)
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
}
}
}

View File

@ -1,20 +1,18 @@
package prog8.compiler.target.cpu6502.codegen.assignment
package prog8.codegen.cpu6502.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType
import prog8.compilerinterface.builtinFunctionReturnType
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen
import prog8.codegen.cpu6502.VariableAllocator
import prog8.compiler.builtinFunctionReturnType
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen)
internal class AssignmentAsmGen(private val program: Program,
private val asmgen: AsmGen,
private val allocator: VariableAllocator) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, allocator)
fun translate(assignment: Assignment) {
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
@ -88,7 +86,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue")
asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue)")
assignFloatFromAY(assign.target)
}
else ->
@ -125,17 +123,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
SourceStorageKind.MEMORY -> {
fun assignViaExprEval(expression: Expression) {
assignExpressionToVariable(expression, "P8ZP_SCRATCH_W2", DataType.UWORD, assign.target.scope)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
assignRegisterByte(assign.target, CpuRegister.A)
}
val value = assign.source.memory!!
when (value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
is NumericLiteral -> {
val address = (value.addressExpression as NumericLiteral).number.toUInt()
assignMemoryByte(assign.target, address, null)
}
is IdentifierReference -> {
@ -152,140 +147,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
SourceStorageKind.EXPRESSION -> {
when(val value = assign.source.expression!!) {
is AddressOf -> {
val sourceName = asmgen.asmSymbolName(value.identifier)
assignAddressOf(assign.target, sourceName)
}
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
is IdentifierReference -> throw AssemblyError("source kind should have been variable")
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
is FunctionCall -> {
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
asmgen.saveXbeforeCall(value)
asmgen.translateFunctionCall(value, true)
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
when (returnValue.first) {
DataType.STR -> {
asmgen.restoreXafterCall(value)
when(assign.target.datatype) {
DataType.UWORD -> {
// assign the address of the string result value
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
// copy the actual string result into the target string variable
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
else -> throw AssemblyError("weird target dt")
}
}
DataType.FLOAT -> {
// float result from function sits in FAC1
asmgen.restoreXafterCall(value)
assignFAC1float(assign.target)
}
else -> {
// do NOT restore X register before assigning the result values first
when (returnValue.second.registerOrPair) {
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
else -> {
val sflag = returnValue.second.statusflag
if(sflag!=null)
assignStatusFlagByte(assign.target, sflag)
else
throw AssemblyError("should be just one register byte result value")
}
}
// we've processed the result value in the X register by now, so it's now finally safe to restore it
asmgen.restoreXafterCall(value)
}
}
}
is BuiltinFunctionStatementPlaceholder -> {
val signature = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(value, signature, false, assign.target.register)
if(assign.target.register==null) {
// still need to assign the result to the target variable/etc.
val returntype = builtinFunctionReturnType(sub.name, value.args, program)
if(!returntype.isKnown)
throw AssemblyError("unknown dt")
when(returntype.getOr(DataType.UNDEFINED)) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
DataType.STR -> {
when (assign.target.datatype) {
DataType.STR -> {
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
DataType.UWORD -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
else -> throw AssemblyError("str return value type mismatch with target")
}
}
DataType.FLOAT -> {
// float result from function sits in FAC1
assignFAC1float(assign.target)
}
else -> throw AssemblyError("weird result type")
}
}
}
else -> {
throw AssemblyError("weird func call")
}
}
}
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
val target = virtualRegsToVariables(assign.target)
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
else -> throw AssemblyError("invalid prefix operator")
}
}
else -> {
// Everything else just evaluate via the stack.
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...)
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
asmgen.translateExpression(value)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
}
}
assignExpression(assign)
}
SourceStorageKind.REGISTER -> {
asmgen.assignRegister(assign.source.register!!, assign.target)
@ -297,6 +159,258 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignExpression(assign: AsmAssignment) {
when(val value = assign.source.expression!!) {
is AddressOf -> {
val sourceName = asmgen.asmSymbolName(value.identifier)
assignAddressOf(assign.target, sourceName)
}
is NumericLiteral -> throw AssemblyError("source kind should have been literalnumber")
is IdentifierReference -> throw AssemblyError("source kind should have been variable")
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
is FunctionCallExpression -> {
val sub = value.target.targetSubroutine(program)!!
asmgen.saveXbeforeCall(value)
asmgen.translateFunctionCall(value, true)
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
when (returnValue.first) {
DataType.STR -> {
asmgen.restoreXafterCall(value)
when(assign.target.datatype) {
DataType.UWORD -> {
// assign the address of the string result value
assignRegisterpairWord(assign.target, RegisterOrPair.AY)
}
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
throw AssemblyError("stringvalue assignment should have been replaced by a call to strcpy")
}
else -> throw AssemblyError("weird target dt")
}
}
DataType.FLOAT -> {
// float result from function sits in FAC1
asmgen.restoreXafterCall(value)
assignFAC1float(assign.target)
}
else -> {
// do NOT restore X register before assigning the result values first
when (returnValue.second.registerOrPair) {
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
RegisterOrPair.Y -> assignRegisterByte(assign.target, CpuRegister.Y)
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
else -> {
val sflag = returnValue.second.statusflag
if(sflag!=null)
assignStatusFlagByte(assign.target, sflag)
else
throw AssemblyError("should be just one register byte result value")
}
}
// we've processed the result value in the X register by now, so it's now finally safe to restore it
asmgen.restoreXafterCall(value)
}
}
}
is BuiltinFunctionCall -> {
asmgen.translateBuiltinFunctionCallExpression(value, false, assign.target.register)
if(assign.target.register==null) {
// still need to assign the result to the target variable/etc.
val returntype = builtinFunctionReturnType(value.name)
if(!returntype.isKnown)
throw AssemblyError("unknown dt")
when(returntype.getOr(DataType.UNDEFINED)) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
DataType.STR -> {
when (assign.target.datatype) {
DataType.STR -> {
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
DataType.UWORD -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
else -> throw AssemblyError("str return value type mismatch with target")
}
}
DataType.FLOAT -> {
// float result from function sits in FAC1
assignFAC1float(assign.target)
}
else -> throw AssemblyError("weird result type")
}
}
}
is PrefixExpression -> {
// first assign the value to the target then apply the operator in place on the target.
translateNormalAssignment(AsmAssignment(
AsmAssignSource.fromAstSource(value.expression, program, asmgen),
assign.target,
false, program.memsizer, assign.position
))
val target = virtualRegsToVariables(assign.target)
when(value.operator) {
"+" -> {}
"-" -> augmentableAsmGen.inplaceNegate(target, target.datatype)
"~" -> augmentableAsmGen.inplaceInvert(target, target.datatype)
"not" -> augmentableAsmGen.inplaceBooleanNot(target, target.datatype)
else -> throw AssemblyError("invalid prefix operator")
}
}
is ContainmentCheck -> {
containmentCheckIntoA(value)
assignRegisterByte(assign.target, CpuRegister.A)
}
is PipeExpression -> {
asmgen.translatePipeExpression(value.source, value.segments, value, false, false)
val resultDt = value.inferType(program)
val register =
if(resultDt.isBytes) RegisterOrPair.A
else if(resultDt.isWords) RegisterOrPair.AY
else if(resultDt istype DataType.FLOAT) RegisterOrPair.FAC1
else throw AssemblyError("invalid dt")
asmgen.assignRegister(register, assign.target)
}
is BinaryExpression -> {
if(value.operator in ComparisonOperators) {
// TODO real optimized code for comparison expressions that yield a boolean result value
assignConstantByte(assign.target, 0)
val origTarget = assign.target.origAstTarget
if(origTarget!=null) {
val assignTrue = AnonymousScope(mutableListOf(
Assignment(origTarget, NumericLiteral.fromBoolean(true, assign.position), AssignmentOrigin.ASMGEN, assign.position)
), assign.position)
val assignFalse = AnonymousScope(mutableListOf(), assign.position)
val ifelse = IfElse(value.copy(), assignTrue, assignFalse, assign.position)
ifelse.linkParents(value)
asmgen.translate(ifelse)
}
else {
// no orig ast assign target so can't use the workaround, so fallback to stack eval
fallbackToStackEval(assign)
}
} else {
// All remaining binary expressions just evaluate via the stack for now.
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...)
fallbackToStackEval(assign)
}
}
else -> throw AssemblyError("weird assignment value type $value")
}
}
private fun fallbackToStackEval(assign: AsmAssignment) {
// TODO DON'T STACK-EVAL... perhaps by using a temp var? so that it becomes augmentable assignment expression?
// or don't try to solve it here in this one case and rather rewrite the whole stack based value evaluation.
// this routine is called for assigning a binaryexpression value:
// - if it's a boolean comparison expression and the workaround isn't possible (no origTarget ast node)
// - for all other binary expressions.
asmgen.translateExpression(assign.source.expression!!)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
if (assign.target.kind != TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
}
private fun containmentCheckIntoA(containment: ContainmentCheck) {
val elementDt = containment.element.inferType(program)
val variable = (containment.iterable as? IdentifierReference)?.targetVarDecl(program)
?: throw AssemblyError("invalid containment iterable type")
if(variable.origin!=VarDeclOrigin.USERCODE) {
when(variable.datatype) {
DataType.STR -> {
require(elementDt.isBytes)
val stringVal = variable.value as StringLiteral
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
asmgen.restoreRegisterLocal(CpuRegister.A)
asmgen.out(" ldy #${stringVal.value.length}")
asmgen.out(" jsr prog8_lib.containment_bytearray")
return
}
DataType.ARRAY_F -> {
// require(elementDt istype DataType.FLOAT)
throw AssemblyError("containment check of floats not supported")
}
in ArrayDatatypes -> {
require(elementDt.isInteger)
val arrayVal = variable.value as ArrayLiteral
val dt = elementDt.getOr(DataType.UNDEFINED)
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
when(dt) {
in ByteDatatypes -> {
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
asmgen.restoreRegisterLocal(CpuRegister.A)
asmgen.out(" ldy #${arrayVal.value.size}")
asmgen.out(" jsr prog8_lib.containment_bytearray")
}
in WordDatatypes -> {
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
asmgen.out(" ldy #${arrayVal.value.size}")
asmgen.out(" jsr prog8_lib.containment_wordarray")
}
else -> throw AssemblyError("invalid dt")
}
return
}
else -> throw AssemblyError("invalid dt")
}
}
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
when(variable.datatype) {
DataType.STR -> {
// use subroutine
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
asmgen.restoreRegisterLocal(CpuRegister.A)
val stringVal = variable.value as StringLiteral
asmgen.out(" ldy #${stringVal.value.length}")
asmgen.out(" jsr prog8_lib.containment_bytearray")
return
}
DataType.ARRAY_F -> {
throw AssemblyError("containment check of floats not supported")
}
DataType.ARRAY_B, DataType.ARRAY_UB -> {
val arrayVal = variable.value as ArrayLiteral
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
asmgen.saveRegisterLocal(CpuRegister.A, containment.definingSubroutine!!)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
asmgen.restoreRegisterLocal(CpuRegister.A)
asmgen.out(" ldy #${arrayVal.value.size}")
asmgen.out(" jsr prog8_lib.containment_bytearray")
return
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
val arrayVal = variable.value as ArrayLiteral
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
asmgen.out(" ldy #${arrayVal.value.size}")
asmgen.out(" jsr prog8_lib.containment_wordarray")
return
}
else -> throw AssemblyError("invalid dt")
}
}
private fun assignStatusFlagByte(target: AsmAssignTarget, statusflag: Statusflag) {
when(statusflag) {
Statusflag.Pc -> {
@ -317,9 +431,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
private fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: Expression, origTypeCastExpression: TypecastExpression) {
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.getOr(DataType.UNDEFINED)
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(valueDt==targetDt)
throw AssemblyError("type cast to identical dt should have been removed")
@ -341,16 +453,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
assignRegisterByte(target, CpuRegister.A)
}
when (value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toUInt()
is NumericLiteral -> {
val address = (value.addressExpression as NumericLiteral).number.toUInt()
assignMemoryByteIntoWord(target, address, null)
return
}
@ -372,7 +481,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
}
is NumericLiteralValue -> throw AssemblyError("a cast of a literal value should have been const-folded away")
is NumericLiteral -> throw AssemblyError("a cast of a literal value should have been const-folded away")
else -> {}
}
@ -450,6 +559,18 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
if(target.kind==TargetStorageKind.REGISTER) {
if(valueDt==DataType.FLOAT && target.datatype!=DataType.FLOAT) {
// have to typecast the float number on the fly down to an integer
assignExpressionToRegister(value, RegisterOrPair.FAC1, target.datatype in SignedDatatypes)
assignTypeCastedFloatFAC1("P8ZP_SCRATCH_W1", target.datatype)
assignVariableToRegister("P8ZP_SCRATCH_W1", target.register!!, target.datatype in SignedDatatypes)
} else {
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
}
return
}
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
when(valueDt) {
DataType.UBYTE -> {
@ -482,7 +603,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
val lsb = BuiltinFunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
lsb.linkParents(value.parent)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
val assign = AsmAssignment(src, target, false, program.memsizer, value.position)
@ -826,8 +947,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
DataType.FLOAT -> {
asmgen.out("""
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
lda #<(${target.asmVarname}+$scaledIdx)
ldy #>(${target.asmVarname}+$scaledIdx)
jsr floats.pop_float
""")
}
@ -1039,8 +1160,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
ldy #>$sourceName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}+$scaledIdx
ldy #>${target.asmVarname}+$scaledIdx
lda #<(${target.asmVarname}+$scaledIdx)
ldy #>(${target.asmVarname}+$scaledIdx)
jsr floats.copy_float
""")
}
@ -1143,7 +1264,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to mem byte")
TargetStorageKind.REGISTER -> {
if (target.register!! != RegisterOrPair.FAC1)
throw AssemblyError("can't assign Fac1 float to another fac register")
throw AssemblyError("can't assign Fac1 float to another register")
}
TargetStorageKind.STACK -> asmgen.out(" jsr floats.push_fac1")
}
@ -1192,17 +1313,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda $sourceName
sta ${target.asmVarname}
lda $sourceName+1
sta ${target.asmVarname}+1
lda $sourceName+2
sta ${target.asmVarname}+2
lda $sourceName+3
sta ${target.asmVarname}+3
lda $sourceName+4
sta ${target.asmVarname}+4
""")
lda #<$sourceName
ldy #>$sourceName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #>${target.asmVarname}
jsr floats.copy_float""")
}
TargetStorageKind.ARRAY -> {
asmgen.out("""
@ -1323,7 +1440,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
pha
ora #$7f
bmi +
ldx #0
lda #0
+ tax
pla""")
RegisterOrPair.AY -> asmgen.out("""
@ -1331,7 +1448,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
pha
ora #$7f
bmi +
ldy #0
lda #0
+ tay
pla""")
RegisterOrPair.XY -> asmgen.out("""
@ -1339,9 +1456,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
tax
ora #$7f
bmi +
ldy #0
lda #0
+ tay""")
else -> throw AssemblyError("only reg pairs are words")
in Cx16VirtualRegisters -> {
val regname = wordtarget.register.name.lowercase()
asmgen.out("""
lda $sourceName
sta cx16.$regname
ora #$7f
bmi +
lda #0
+ sta cx16.$regname+1""")
}
else -> throw AssemblyError("only reg pairs allowed as word target ${wordtarget.register}")
}
}
TargetStorageKind.STACK -> {
@ -1392,7 +1519,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda $sourceName")
RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda $sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName")
else -> throw AssemblyError("only reg pairs are words")
in Cx16VirtualRegisters -> {
val regname = wordtarget.register.name.lowercase()
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceName | sta cx16.$regname | stz cx16.$regname+1")
else
asmgen.out(" lda $sourceName | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
}
else -> throw AssemblyError("only reg pairs allowed as word target")
}
}
TargetStorageKind.STACK -> {
@ -1660,7 +1794,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" stz ${target.asmVarname} | stz ${target.asmVarname}+1")
}
TargetStorageKind.MEMORY -> {
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}")
throw AssemblyError("memory is bytes not words")
}
TargetStorageKind.ARRAY -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y)
@ -1759,7 +1893,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" stz ${target.asmVarname} ")
}
TargetStorageKind.MEMORY -> {
asmgen.out(" lda #${byte.toHex()}")
asmgen.out(" lda #0")
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
@ -1905,7 +2039,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
TargetStorageKind.REGISTER -> {
val floatConst = asmgen.getFloatAsmConst(float)
val floatConst = allocator.getFloatAsmConst(float)
when(target.register!!) {
RegisterOrPair.FAC1 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.MOVFM")
RegisterOrPair.FAC2 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.CONUPK")
@ -1913,27 +2047,23 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
TargetStorageKind.STACK -> {
val floatConst = asmgen.getFloatAsmConst(float)
val floatConst = allocator.getFloatAsmConst(float)
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
}
}
} else {
// non-zero value
val constFloat = asmgen.getFloatAsmConst(float)
val constFloat = allocator.getFloatAsmConst(float)
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out("""
lda $constFloat
sta ${target.asmVarname}
lda $constFloat+1
sta ${target.asmVarname}+1
lda $constFloat+2
sta ${target.asmVarname}+2
lda $constFloat+3
sta ${target.asmVarname}+3
lda $constFloat+4
sta ${target.asmVarname}+4
""")
lda #<$constFloat
ldy #>$constFloat
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}
ldy #>${target.asmVarname}
jsr floats.copy_float""")
}
TargetStorageKind.ARRAY -> {
val arrayVarName = target.asmVarname
@ -1941,17 +2071,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
if (constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
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
""")
lda #<$constFloat
ldy #>$constFloat
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<($arrayVarName+$indexValue)
ldy #>($arrayVarName+$indexValue)
jsr floats.copy_float""")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out("""
@ -1970,7 +2096,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
TargetStorageKind.REGISTER -> {
val floatConst = asmgen.getFloatAsmConst(float)
val floatConst = allocator.getFloatAsmConst(float)
when(target.register!!) {
RegisterOrPair.FAC1 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.MOVFM")
RegisterOrPair.FAC2 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.CONUPK")
@ -1978,7 +2104,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
TargetStorageKind.STACK -> {
val floatConst = asmgen.getFloatAsmConst(float)
val floatConst = allocator.getFloatAsmConst(float)
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
}
}
@ -2134,58 +2260,20 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
private fun storeRegisterAInMemoryAddress(memoryAddress: DirectMemoryWrite) {
val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
val addressLv = addressExpr as? NumericLiteral
fun storeViaExprEval() {
when(addressExpr) {
is NumericLiteralValue, is IdentifierReference -> {
is NumericLiteral, is IdentifierReference -> {
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
}
else -> {
// same as above but we need to save the A register
asmgen.out(" pha")
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.out(" pla")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
}
}
}
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
val sourceName = asmgen.asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.scopedName.joinToString(".")
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
asmgen.out(" sta ($sourceName)")
} else {
asmgen.out("""
ldy $sourceName
sty P8ZP_SCRATCH_W2
ldy $sourceName+1
sty P8ZP_SCRATCH_W2+1
sta (P8ZP_SCRATCH_W2)""")
}
} else {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
asmgen.out(" ldy #0 | sta ($sourceName),y")
} else {
asmgen.out("""
ldy $sourceName
sty P8ZP_SCRATCH_W2
ldy $sourceName+1
sty P8ZP_SCRATCH_W2+1
ldy #0
sta (P8ZP_SCRATCH_W2),y""")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
}
}
}
@ -2195,7 +2283,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" sta ${addressLv.number.toHex()}")
}
addressExpr is IdentifierReference -> {
storeAIntoPointerVar(addressExpr)
asmgen.storeAIntoPointerVar(addressExpr)
}
addressExpr is BinaryExpression -> {
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, true))
@ -2213,10 +2301,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, scope, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign)
if(expr.inferType(program) istype DataType.FLOAT && dt!=DataType.FLOAT) {
throw AssemblyError("can't directly assign a FLOAT expression to an integer variable $expr")
} else {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, scope, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign)
}
}
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair, signed: Boolean) {

View File

@ -1,18 +1,18 @@
package prog8.compiler.target.cpu6502.codegen.assignment
package prog8.codegen.cpu6502.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.target.AssemblyError
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compilerinterface.CpuType
import prog8.code.core.*
import prog8.codegen.cpu6502.AsmGen
import prog8.codegen.cpu6502.VariableAllocator
internal class AugmentableAssignmentAsmGen(private val program: Program,
private val assignmentAsmGen: AssignmentAsmGen,
private val asmgen: AsmGen
private val asmgen: AsmGen,
private val allocator: VariableAllocator
) {
fun translate(assign: AsmAssignment) {
require(assign.isAugmentable)
@ -23,9 +23,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// A = -A , A = +A, A = ~A, A = not A
val target = assignmentAsmGen.virtualRegsToVariables(assign.target)
val itype = value.inferType(program)
if(!itype.isKnown)
throw AssemblyError("unknown dt")
val type = itype.getOr(DataType.UNDEFINED)
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
when (value.operator) {
"+" -> {}
"-" -> inplaceNegate(target, type)
@ -47,7 +45,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
return inplaceModification(target, binExpr.operator, binExpr.right)
}
if (binExpr.operator in associativeOperators) {
if (binExpr.operator in AssociativeOperators) {
if (binExpr.right isSameAs astTarget) {
// A = 5 <operator> A
return inplaceModification(target, binExpr.operator, binExpr.left)
@ -113,7 +111,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
if(leftBinExpr.right isSameAs astTarget) {
// X = (Left <oper> X) <oper> Something
if(leftBinExpr.operator in associativeOperators) {
if(leftBinExpr.operator in AssociativeOperators) {
inplaceModification(target, leftBinExpr.operator, leftBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.right)
return
@ -125,7 +123,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
if(leftBinExpr==null && rightBinExpr!=null) {
if(rightBinExpr.left isSameAs astTarget) {
// X = Something <oper> (X <oper> Right)
if(binExpr.operator in associativeOperators) {
if(binExpr.operator in AssociativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.right)
inplaceModification(target, binExpr.operator, binExpr.left)
return
@ -135,7 +133,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
if(rightBinExpr.right isSameAs astTarget) {
// X = Something <oper> (Left <oper> X)
if(binExpr.operator in associativeOperators && rightBinExpr.operator in associativeOperators) {
if(binExpr.operator in AssociativeOperators && rightBinExpr.operator in AssociativeOperators) {
inplaceModification(target, rightBinExpr.operator, rightBinExpr.left)
inplaceModification(target, binExpr.operator, binExpr.left)
return
@ -153,7 +151,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// the asm-gen code can deal with situations where you want to assign a byte into a word.
// it will create the most optimized code to do this (so it type-extends for us).
// But we can't deal with writing a word into a byte - explicit typeconversion is required
val value = if(program.memsizer.memorySize(origValue.inferType(program).getOr(DataType.UNDEFINED)) > program.memsizer.memorySize(target.datatype)) {
val value = if(program.memsizer.memorySize(origValue.inferType(program).getOrElse { throw AssemblyError("unknown dt") }) > program.memsizer.memorySize(target.datatype)) {
val typecast = TypecastExpression(origValue, target.datatype, true, origValue.position)
typecast.linkParents(origValue.parent)
typecast
@ -162,7 +160,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
origValue
}
val valueLv = (value as? NumericLiteralValue)?.number
val valueLv = (value as? NumericLiteral)?.number
val ident = value as? IdentifierReference
val memread = value as? DirectMemoryRead
@ -175,7 +173,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ident != null -> inplaceModification_byte_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
}
else -> inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
@ -187,7 +185,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ident != null -> inplaceModification_word_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
memread != null -> inplaceModification_word_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator))
return
inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
}
else -> inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
@ -198,7 +197,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble(), target.scope!!)
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident, target.scope!!)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
}
else -> inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
@ -210,15 +209,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
TargetStorageKind.MEMORY -> {
val memory = target.memory!!
when (memory.addressExpression) {
is NumericLiteralValue -> {
val addr = (memory.addressExpression as NumericLiteralValue).number.toInt()
is NumericLiteral -> {
val addr = (memory.addressExpression as NumericLiteral).number.toInt()
// re-use code to assign a variable, instead this time, use a direct memory address
when {
valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_variable(addr.toHex(), DataType.UBYTE, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
}
else -> inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
@ -230,14 +229,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
valueLv != null -> inplaceModification_byte_litval_to_pointer(pointer, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_pointer(pointer, operator, ident)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_pointer(pointer, operator, value)
}
else -> inplaceModification_byte_value_to_pointer(pointer, operator, value)
}
}
else -> {
// TODO OTHER EVALUATION HERE, don't use the estack to transfer the address to read/write from
// TODO use some other evaluation here; don't use the estack to transfer the address to read/write from
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, memory.definingSubroutine))
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
when {
@ -245,7 +244,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, memread)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
}
else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
@ -256,7 +255,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
TargetStorageKind.ARRAY -> {
with(target.array!!.indexer) {
val indexNum = indexExpr as? NumericLiteralValue
val indexNum = indexExpr as? NumericLiteral
val indexVar = indexExpr as? IdentifierReference
when {
indexNum!=null -> {
@ -268,7 +267,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ident != null -> inplaceModification_byte_variable_to_variable(targetVarName, target.datatype, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable(targetVarName, target.datatype, operator, memread)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
}
else -> inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
@ -280,7 +279,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ident != null -> inplaceModification_word_variable_to_variable(targetVarName, target.datatype, operator, ident)
memread != null -> inplaceModification_word_memread_to_variable(targetVarName, target.datatype, operator, memread)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
}
else -> inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
@ -291,7 +290,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
valueLv != null -> inplaceModification_float_litval_to_variable(targetVarName, operator, valueLv.toDouble(), target.scope!!)
ident != null -> inplaceModification_float_variable_to_variable(targetVarName, operator, ident, target.scope!!)
value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
}
else -> inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
@ -350,12 +349,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
private fun tryRemoveRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
private fun tryInplaceModifyWithRemovedRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
if (target.datatype == value.type) {
val childIDt = value.expression.inferType(program)
if(!childIDt.isKnown)
throw AssemblyError("unknown dt")
val childDt = childIDt.getOr(DataType.UNDEFINED)
val childDt = childIDt.getOrElse { throw AssemblyError("unknown dt") }
if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) {
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
// (works for integer types, not for float.)
@ -368,13 +365,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_byte_value_to_pointer(pointervar: IdentifierReference, operator: String, value: Expression) {
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
when (operator) {
// note: ** (power) operator requires floats.
"+" -> asmgen.out(" clc | adc P8ZP_SCRATCH_B1")
"-" -> asmgen.out(" sec | sbc P8ZP_SCRATCH_B1")
"*" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.multiply_bytes | ldy #0")
"/" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm | tya | ldy #0")
"%" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm | ldy #0")
"*" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.multiply_bytes")
"/" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm | tya")
"%" -> asmgen.out(" ldy P8ZP_SCRATCH_B1 | jsr math.divmod_ub_asm")
"<<" -> {
asmgen.out("""
ldy P8ZP_SCRATCH_B1
@ -398,8 +396,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" sta ($sourceName),y")
asmgen.storeAIntoZpPointerVar(sourceName)
}
private fun inplaceModification_byte_variable_to_pointer(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
@ -410,9 +407,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// note: ** (power) operator requires floats.
"+" -> asmgen.out(" clc | adc $otherName")
"-" -> asmgen.out(" sec | sbc $otherName")
"*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes | ldy #0")
"/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya | ldy #0")
"%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | ldy #0")
"*" -> asmgen.out(" ldy $otherName | jsr math.multiply_bytes")
"/" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm | tya")
"%" -> asmgen.out(" ldy $otherName | jsr math.divmod_ub_asm")
"<<" -> {
asmgen.out("""
ldy $otherName
@ -436,7 +433,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"^", "xor" -> asmgen.out(" eor $otherName")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
asmgen.out(" sta ($sourceName),y")
asmgen.storeAIntoZpPointerVar(sourceName)
}
private fun inplaceModification_byte_litval_to_pointer(pointervar: IdentifierReference, operator: String, value: Int) {
@ -445,63 +442,63 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"+" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" clc | adc #$value")
asmgen.out(" sta ($sourceName),y")
asmgen.storeAIntoZpPointerVar(sourceName)
}
"-" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" sec | sbc #$value")
asmgen.out(" sta ($sourceName),y")
asmgen.storeAIntoZpPointerVar(sourceName)
}
"*" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value in asmgen.optimizedByteMultiplications)
asmgen.out(" jsr math.mul_byte_${value}")
else
asmgen.out(" ldy #$value | jsr math.multiply_bytes | ldy #0")
asmgen.out(" sta ($sourceName),y")
asmgen.out(" ldy #$value | jsr math.multiply_bytes")
asmgen.storeAIntoZpPointerVar(sourceName)
}
"/" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya | ldy #0")
asmgen.out(" sta ($sourceName),y")
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | tya")
asmgen.storeAIntoZpPointerVar(sourceName)
}
"%" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
if(value==0)
throw AssemblyError("division by zero")
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm | ldy #0")
asmgen.out(" sta ($sourceName),y")
asmgen.out(" ldy #$value | jsr math.divmod_ub_asm")
asmgen.storeAIntoZpPointerVar(sourceName)
}
"<<" -> {
if (value > 0) {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
repeat(value) { asmgen.out(" asl a") }
asmgen.out(" sta ($sourceName),y")
repeat(value) { asmgen.out(" asl a") }
asmgen.storeAIntoZpPointerVar(sourceName)
}
}
">>" -> {
if (value > 0) {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
repeat(value) { asmgen.out(" lsr a") }
asmgen.out(" sta ($sourceName),y")
repeat(value) { asmgen.out(" lsr a") }
asmgen.storeAIntoZpPointerVar(sourceName)
}
}
"&", "and" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" and #$value")
asmgen.out(" sta ($sourceName),y")
asmgen.storeAIntoZpPointerVar(sourceName)
}
"|", "or" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" ora #$value")
asmgen.out(" sta ($sourceName),y")
asmgen.storeAIntoZpPointerVar(sourceName)
}
"^", "xor" -> {
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
asmgen.out(" eor #$value")
asmgen.out(" sta ($sourceName),y")
asmgen.storeAIntoZpPointerVar(sourceName)
}
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
@ -578,6 +575,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out(" eor $name | sta $name")
}
"==" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
cmp $name
beq +
lda #0
beq ++
+ lda #1
+ sta $name""")
}
"!=" -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
cmp $name
beq +
lda #1
bne ++
+ lda #0
+ sta $name""")
}
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -635,6 +652,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name")
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name")
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name")
"==" -> {
asmgen.out("""
lda $otherName
cmp $name
beq +
lda #0
bne ++
+ lda #1
+ sta $name""")
}
"!=" -> {
asmgen.out("""
lda $otherName
cmp $name
beq +
lda #1
bne ++
+ lda #0
+ sta $name""")
}
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -706,6 +743,26 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> asmgen.out(" lda $name | and #$value | sta $name")
"|", "or" -> asmgen.out(" lda $name | ora #$value | sta $name")
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
"==" -> {
asmgen.out("""
lda $name
cmp #$value
beq +
lda #0
beq ++
+ lda #1
+ sta $name""")
}
"!=" -> {
asmgen.out("""
lda $name
cmp #$value
beq +
lda #1
bne ++
+ lda #0
+ sta $name""")
}
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -1010,6 +1067,18 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else
asmgen.out(" lda #0 | sta $name | sta $name+1")
}
value == 0x00ff -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
}
value == 0xff00 -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
}
value and 255 == 0 -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
@ -1089,33 +1158,38 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""")
else
asmgen.out("""
ldy #0
ldy #255
lda $otherName
bpl +
dey ; sign extend
+ sty P8ZP_SCRATCH_B1
lda $name
iny ; sign extend
+ eor #255
sec
sbc $otherName
adc $name
sta $name
lda $name+1
sbc P8ZP_SCRATCH_B1
tya
adc $name+1
sta $name+1""")
}
"*" -> {
asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ZP_SCRATCH_W1+1")
else
asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1")
if(valueDt==DataType.UBYTE) {
asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1")
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ZP_SCRATCH_W1+1")
else
asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1")
} else {
asmgen.out(" lda $otherName")
asmgen.signExtendAYlsb(valueDt)
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
}
asmgen.out("""
lda $name
ldy $name+1
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
lda $name
ldy $name+1
jsr math.multiply_words
lda math.multiply_words.result
sta $name
lda math.multiply_words.result+1
sta $name+1""")
}
"/" -> {
if(dt==DataType.UWORD) {
@ -1292,9 +1366,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
// because the value is evaluated onto the eval stack (=slow).
val valueiDt = value.inferType(program)
if(!valueiDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueiDt.getOr(DataType.UNDEFINED)
val valueDt = valueiDt.getOrElse { throw AssemblyError("unknown dt") }
fun multiplyVarByWordInAY() {
asmgen.out("""
@ -1373,29 +1445,28 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta $name+1""")
}
"-" -> {
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_REG", valueDt, null)
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", valueDt, null)
if(valueDt==DataType.UBYTE)
asmgen.out("""
lda $name
sec
sbc P8ZP_SCRATCH_REG
sbc P8ZP_SCRATCH_B1
sta $name
bcs +
dec $name+1
+""")
else
asmgen.out("""
ldy #0
lda P8ZP_SCRATCH_REG
ldy #255
lda P8ZP_SCRATCH_B1
bpl +
dey ; sign extend
+ sty P8ZP_SCRATCH_B1
lda $name
iny ; sign extend
+ eor #255
sec
sbc P8ZP_SCRATCH_REG
adc $name
sta $name
lda $name+1
sbc P8ZP_SCRATCH_B1
tya
adc $name+1
sta $name+1""")
}
"*" -> {
@ -1519,14 +1590,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(value, RegisterOrPair.FAC1)
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
"**" -> {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
jsr floats.FPWRT
""")
}
"+" -> {
asmgen.out("""
lda #<$name
@ -1573,28 +1636,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
val otherName = asmgen.asmVariableName(ident)
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
"**" -> {
if(asmgen.haveFPWR()) {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$otherName
ldy #>$otherName
jsr floats.FPWR
""")
} else
// cx16 doesn't have FPWR() only FPWRT()
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$otherName
ldy #>$otherName
jsr floats.MOVFM
jsr floats.FPWRT
""")
}
"+" -> {
asmgen.out("""
lda #<$name
@ -1647,31 +1688,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: Subroutine) {
val constValueName = asmgen.getFloatAsmConst(value)
val constValueName = allocator.getFloatAsmConst(value)
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
"**" -> {
if(asmgen.haveFPWR()) {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$constValueName
ldy #>$constValueName
jsr floats.FPWR
""")
} else
// cx16 doesn't have FPWR() only FPWRT()
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$constValueName
ldy #>$constValueName
jsr floats.MOVFM
jsr floats.FPWRT
""")
}
"+" -> {
if (value == 0.0)
return
@ -1796,8 +1815,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
TargetStorageKind.MEMORY -> {
val mem = target.memory!!
when (mem.addressExpression) {
is NumericLiteralValue -> {
val addr = (mem.addressExpression as NumericLiteralValue).number.toHex()
is NumericLiteral -> {
val addr = (mem.addressExpression as NumericLiteral).number.toHex()
asmgen.out("""
lda $addr
beq +
@ -1811,17 +1830,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
beq +
lda #1
+ eor #1""")
asmgen.out(" sta ($sourceName),y")
asmgen.storeAIntoZpPointerVar(sourceName)
}
else -> {
asmgen.assignExpressionToVariable(mem.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
asmgen.loadAFromZpPointerVar("P8ZP_SCRATCH_W2")
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W2),y
beq +
lda #1
+ eor #1
sta (P8ZP_SCRATCH_W2),y""")
+ eor #1""")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
}
}
}
@ -1831,18 +1849,18 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
cmp #0
beq +
lda #1
+ eor #1""")
+ eor #1""")
RegisterOrPair.X -> asmgen.out("""
txa
beq +
lda #1
+ eor #1
+ eor #1
tax""")
RegisterOrPair.Y -> asmgen.out("""
tya
beq +
lda #1
+ eor #1
+ eor #1
tay""")
else -> throw AssemblyError("invalid reg dt for byte not")
}
@ -1925,8 +1943,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
TargetStorageKind.MEMORY -> {
val memory = target.memory!!
when (memory.addressExpression) {
is NumericLiteralValue -> {
val addr = (memory.addressExpression as NumericLiteralValue).number.toHex()
is NumericLiteral -> {
val addr = (memory.addressExpression as NumericLiteral).number.toHex()
asmgen.out("""
lda $addr
eor #255
@ -1942,8 +1960,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out("""
ldy #0
lda (P8ZP_SCRATCH_W2),y
eor #255
sta (P8ZP_SCRATCH_W2),y""")
eor #255""")
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
}
}
}
@ -2033,40 +2051,38 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when(target.register!!) { //P8ZP_SCRATCH_REG
RegisterOrPair.AX -> {
asmgen.out("""
sta P8ZP_SCRATCH_REG
stx P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
eor #255
adc #0
pha
lda #0
sbc P8ZP_SCRATCH_REG+1
txa
eor #255
adc #0
tax
pla""")
}
RegisterOrPair.AY -> {
asmgen.out("""
sta P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
eor #255
adc #0
pha
lda #0
sbc P8ZP_SCRATCH_REG+1
tya
eor #255
adc #0
tay
pla""")
}
RegisterOrPair.XY -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_REG+1
lda #0
sec
sbc P8ZP_SCRATCH_REG
txa
eor #255
adc #0
tax
lda #0
sbc P8ZP_SCRATCH_REG+1
tya
eor #255
adc #0
tay""")
}
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
@ -2087,7 +2103,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta ${target.asmVarname}+1
""")
}
TargetStorageKind.STACK -> TODO("no asm gen for stack float negate")
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
else -> throw AssemblyError("weird target kind for inplace negate float ${target.kind}")
}
}

View File

@ -0,0 +1,46 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':codeAst')
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
}
// note: there are no unit tests in this module!

View File

@ -0,0 +1,16 @@
<?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$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="codeAst" />
<orderEntry type="module" module-name="codeCore" />
</component>
</module>

View File

@ -0,0 +1,39 @@
package prog8.codegen.experimental
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyGenerator
import prog8.code.core.IAssemblyProgram
import prog8.code.core.IErrorReporter
/*
NOTE: The goal is to keep the dependencies as lean as possible! For now, we depend only on:
- codeAst (the 'lean' new AST and the SymbolTable)
- codeCore (various base enums and interfaces)
This *should* be enough to build a complete code generator with. But we'll see :)
*/
class AsmGen(internal val program: PtProgram,
internal val symbolTable: SymbolTable,
internal val options: CompilationOptions,
internal val errors: IErrorReporter
): IAssemblyGenerator {
override fun compileToAssembly(): IAssemblyProgram? {
println("\n** experimental code generator **\n")
println("Writing AST into XML form...")
val xmlConv = AstToXmlConverter(program, symbolTable, options)
xmlConv.writeXml()
println("..todo: create assembly program into ${options.outputDir.toAbsolutePath()}..")
return AssemblyProgram("dummy")
}
}

View File

@ -0,0 +1,13 @@
package prog8.codegen.experimental
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyProgram
internal class AssemblyProgram(override val name: String) : IAssemblyProgram
{
override fun assemble(options: CompilationOptions): Boolean {
println("..todo: assemble code into binary..")
return true
}
}

View File

@ -0,0 +1,666 @@
package prog8.codegen.experimental
import prog8.code.*
import prog8.code.ast.*
import prog8.code.core.*
import javax.xml.stream.XMLOutputFactory
import kotlin.io.path.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.div
/*
NOTE: The goal is to keep the dependencies as lean as possible! For now, we depend only on:
- codeAst (the 'lean' new AST and the SymbolTable)
- codeCore (various base enums and interfaces)
This *should* be enough to build a complete code generator with. But we'll see :)
*/
class AstToXmlConverter(internal val program: PtProgram,
internal val symbolTable: SymbolTable,
internal val options: CompilationOptions
) {
private lateinit var xml: IndentingXmlWriter
fun writeXml() {
val writer = (options.outputDir / Path(program.name+"-ast.xml")).toFile().printWriter()
xml = IndentingXmlWriter(XMLOutputFactory.newFactory().createXMLStreamWriter(writer))
xml.doc()
xml.elt("program")
xml.attr("name", program.name)
xml.startChildren()
writeOptions(options)
program.children.forEach { writeNode(it) }
writeSymboltable(symbolTable)
xml.endElt()
xml.endDoc()
xml.close()
}
private fun writeSymboltable(st: SymbolTable) {
xml.elt("symboltable")
xml.startChildren()
st.flat.forEach{ (name, entry) ->
xml.elt("entry")
xml.attr("name", name.joinToString("."))
xml.attr("type", entry.type.name)
xml.startChildren()
writeStNode(entry)
xml.endElt()
}
xml.endElt()
}
private fun writeStNode(node: StNode) {
when(node.type) {
StNodeType.GLOBAL,
StNodeType.LABEL,
StNodeType.BLOCK,
StNodeType.BUILTINFUNC,
StNodeType.SUBROUTINE -> {/* no additional info*/}
StNodeType.ROMSUB -> {
node as StRomSub
xml.elt("romsub")
xml.attr("address", node.address.toString())
xml.endElt()
}
StNodeType.STATICVAR -> {
node as StStaticVariable
xml.elt("var")
xml.attr("type", node.dt.name)
xml.attr("zpwish", node.zpwish.name)
if(node.length!=null)
xml.attr("length", node.length.toString())
if(node.initialNumericValue!=null || node.initialArrayValue!=null || node.initialStringValue!=null) {
xml.startChildren()
if(node.initialNumericValue!=null) {
writeNumber(node.dt, node.initialNumericValue!!)
}
if(node.initialStringValue!=null) {
xml.writeTextNode(
"string",
listOf(Pair("encoding", node.initialStringValue!!.second.name)),
node.initialStringValue!!.first,
false
)
}
if(node.initialArrayValue!=null) {
xml.elt("array")
xml.startChildren()
val eltDt = ArrayToElementTypes.getValue(node.dt)
node.initialArrayValue!!.forEach {
if(it.number!=null) {
writeNumber(eltDt, it.number!!)
}
if(it.addressOf!=null) {
xml.elt("addressof")
xml.attr("symbol", it.addressOf!!.joinToString("."))
xml.endElt()
}
}
xml.endElt()
}
}
xml.endElt()
}
StNodeType.MEMVAR -> {
node as StMemVar
xml.writeTextNode("memvar",
listOf(Pair("type", node.dt.name)),
node.address.toString(),
false)
}
StNodeType.CONSTANT -> {
node as StConstant
xml.writeTextNode("const",
listOf(Pair("type", node.dt.name)),
intOrDouble(node.dt, node.value).toString(),
false)
}
}
}
private fun writeOptions(options: CompilationOptions) {
xml.elt("options")
xml.attr("target", options.compTarget.name)
xml.attr("output", options.output.name)
xml.attr("launcher", options.launcher.name)
xml.attr("zeropage", options.zeropage.name)
xml.attr("loadaddress", options.loadAddress.toString())
xml.attr("floatsenabled", options.floats.toString())
xml.attr("nosysinit", options.noSysInit.toString())
xml.attr("dontreinitglobals", options.dontReinitGlobals.toString())
xml.attr("optimize", options.optimize.toString())
if(options.zpReserved.isNotEmpty()) {
xml.startChildren()
options.zpReserved.forEach {
xml.elt("zpreserved")
xml.attr("from", it.first.toString())
xml.attr("to", it.last.toString())
xml.endElt()
}
}
xml.endElt()
}
private fun writeNode(it: PtNode) {
when(it) {
is PtBlock -> write(it)
is PtSub -> write(it)
is PtVariable -> write(it)
is PtAssignment -> write(it)
is PtConstant -> write(it)
is PtAsmSub -> write(it)
is PtAddressOf -> write(it)
is PtArrayIndexer -> write(it)
is PtArray -> write(it)
is PtBinaryExpression -> write(it)
is PtBuiltinFunctionCall -> write(it)
is PtConditionalBranch -> write(it)
is PtContainmentCheck -> write(it)
is PtForLoop -> write(it)
is PtFunctionCall -> write(it)
is PtIdentifier -> write(it)
is PtIfElse -> write(it)
is PtInlineAssembly -> write(it)
is PtIncludeBinary -> write(it)
is PtJump -> write(it)
is PtMemoryByte -> write(it)
is PtMemMapped -> write(it)
is PtNumber -> write(it)
is PtPipe -> write(it)
is PtPostIncrDecr -> write(it)
is PtPrefix -> write(it)
is PtRange -> write(it)
is PtRepeatLoop -> write(it)
is PtReturn -> write(it)
is PtString -> write(it)
is PtTypeCast -> write(it)
is PtWhen -> write(it)
is PtWhenChoice -> write(it)
is PtLabel -> write(it)
is PtNop -> {}
is PtBreakpoint -> write(it)
is PtScopeVarsDecls -> write(it)
is PtNodeGroup -> it.children.forEach { writeNode(it) }
else -> TODO("$it")
}
}
private fun write(vars: PtScopeVarsDecls) {
xml.elt("vars")
xml.startChildren()
vars.children.forEach { writeNode(it) }
xml.endElt()
}
private fun write(breakPt: PtBreakpoint) {
xml.elt("breakpoint")
xml.pos(breakPt.position)
xml.endElt()
}
private fun write(pipe: PtPipe) {
xml.elt("pipe")
xml.attr("type", pipe.type.name)
xml.startChildren()
pipe.children.forEach { writeNode(it) }
xml.endElt()
}
private fun write(array: PtArray) {
xml.elt("array")
xml.attr("type", array.type.name)
xml.startChildren()
array.children.forEach { writeNode(it) }
xml.endElt()
}
private fun write(prefix: PtPrefix) {
xml.elt("prefix")
xml.attr("op", prefix.operator)
xml.attr("type", prefix.type.name)
xml.startChildren()
xml.elt("value")
xml.startChildren()
writeNode(prefix.value)
xml.endElt()
xml.endElt()
}
private fun write(string: PtString) =
xml.writeTextNode("string", listOf(Pair("encoding", string.encoding.name)), string.value, false)
private fun write(rept: PtRepeatLoop) {
xml.elt("repeat")
xml.pos(rept.position)
xml.startChildren()
xml.elt("count")
xml.startChildren()
writeNode(rept.count)
xml.endElt()
writeNode(rept.statements)
xml.endElt()
}
private fun write(branch: PtConditionalBranch) {
xml.elt("conditionalbranch")
xml.attr("condition", branch.condition.name)
xml.pos(branch.position)
xml.startChildren()
xml.elt("true")
xml.startChildren()
writeNode(branch.trueScope)
xml.endElt()
if(branch.falseScope.children.isNotEmpty()) {
xml.elt("false")
xml.startChildren()
writeNode(branch.falseScope)
xml.endElt()
}
xml.endElt()
}
private fun write(check: PtContainmentCheck) {
xml.elt("containment")
xml.attr("type", check.type.name)
xml.startChildren()
xml.elt("element")
xml.startChildren()
writeNode(check.children[0])
xml.endElt()
xml.elt("iterable")
xml.startChildren()
writeNode(check.children[1])
xml.endElt()
xml.endElt()
}
private fun write(range: PtRange) {
xml.elt("range")
xml.attr("type", range.type.name)
xml.startChildren()
xml.elt("from")
xml.startChildren()
writeNode(range.from)
xml.endElt()
xml.elt("to")
xml.startChildren()
writeNode(range.to)
xml.endElt()
xml.elt("step")
xml.startChildren()
writeNode(range.step)
xml.endElt()
xml.endElt()
}
private fun write(forLoop: PtForLoop) {
xml.elt("for")
xml.attr("loopvar", strTargetName(forLoop.variable))
xml.pos(forLoop.position)
xml.startChildren()
xml.elt("iterable")
xml.startChildren()
writeNode(forLoop.iterable)
xml.endElt()
writeNode(forLoop.statements)
xml.endElt()
}
private fun write(membyte: PtMemoryByte) {
xml.elt("membyte")
xml.attr("type", membyte.type.name)
xml.startChildren()
xml.elt("address")
xml.startChildren()
writeNode(membyte.address)
xml.endElt()
xml.endElt()
}
private fun write(whenStmt: PtWhen) {
xml.elt("when")
xml.pos(whenStmt.position)
xml.startChildren()
xml.elt("value")
xml.startChildren()
writeNode(whenStmt.value)
xml.endElt()
xml.elt("choices")
xml.startChildren()
writeNode(whenStmt.choices)
xml.endElt()
xml.endElt()
}
private fun write(choice: PtWhenChoice) {
xml.elt("choice")
if(choice.isElse) {
xml.attr("else", "true")
xml.startChildren()
} else {
xml.startChildren()
xml.elt("values")
xml.startChildren()
writeNode(choice.values)
xml.endElt()
}
writeNode(choice.statements)
xml.endElt()
}
private fun write(inlineAsm: PtInlineAssembly) {
xml.elt("assembly")
xml.pos(inlineAsm.position)
xml.startChildren()
xml.writeTextNode("code", emptyList(), inlineAsm.assembly)
xml.endElt()
}
private fun write(inlineBinary: PtIncludeBinary) {
xml.elt("binary")
xml.attr("filename", inlineBinary.file.absolutePathString())
if(inlineBinary.offset!=null)
xml.attr("offset", inlineBinary.offset!!.toString())
if(inlineBinary.length!=null)
xml.attr("length", inlineBinary.length!!.toString())
xml.pos(inlineBinary.position)
xml.endElt()
}
private fun write(fcall: PtBuiltinFunctionCall) {
xml.elt("builtinfcall")
xml.attr("name", fcall.name)
if(fcall.void)
xml.attr("type", "VOID")
else
xml.attr("type", fcall.type.name)
xml.startChildren()
fcall.children.forEach { writeNode(it) }
xml.endElt()
}
private fun write(cast: PtTypeCast) {
xml.elt("cast")
xml.attr("type", cast.type.name)
xml.startChildren()
writeNode(cast.value)
xml.endElt()
}
private fun write(aix: PtArrayIndexer) {
xml.elt("arrayindexed")
xml.attr("type", aix.type.name)
xml.startChildren()
write(aix.variable)
writeNode(aix.index)
xml.endElt()
}
private fun write(binexpr: PtBinaryExpression) {
xml.elt("binexpr")
xml.attr("op", binexpr.operator)
xml.attr("type", binexpr.type.name)
xml.startChildren()
writeNode(binexpr.left)
writeNode(binexpr.right)
xml.endElt()
}
private fun write(addrof: PtAddressOf) {
xml.elt("addressof")
xml.attr("symbol", strTargetName(addrof.identifier))
xml.endElt()
}
private fun write(fcall: PtFunctionCall) {
xml.elt("fcall")
xml.attr("name", strTargetName(fcall))
if(fcall.void)
xml.attr("type", "VOID")
else
xml.attr("type", fcall.type.name)
xml.pos(fcall.position)
xml.startChildren()
fcall.children.forEach { writeNode(it) }
xml.endElt()
}
private fun write(number: PtNumber) = writeNumber(number.type, number.number)
private fun writeNumber(type: DataType, number: Double) =
xml.writeTextNode("number", listOf(Pair("type", type.name)), intOrDouble(type, number).toString(), false)
private fun write(symbol: PtIdentifier) {
xml.elt("symbol")
xml.attr("name", strTargetName(symbol))
xml.attr("type", symbol.type.name)
xml.endElt()
}
private fun write(assign: PtAssignment) {
xml.elt("assign")
xml.pos(assign.position)
xml.startChildren()
write(assign.target)
writeNode(assign.value)
xml.endElt()
}
private fun write(ifElse: PtIfElse) {
xml.elt("ifelse")
xml.pos(ifElse.position)
xml.startChildren()
xml.elt("condition")
xml.startChildren()
writeNode(ifElse.condition)
xml.endElt()
xml.elt("true")
xml.pos(ifElse.ifScope.position)
xml.startChildren()
writeNode(ifElse.ifScope)
xml.endElt()
if(ifElse.elseScope.children.isNotEmpty()) {
xml.elt("false")
xml.pos(ifElse.elseScope.position)
xml.startChildren()
writeNode(ifElse.elseScope)
xml.endElt()
}
xml.endElt()
}
private fun write(ret: PtReturn) {
xml.elt("return")
if(ret.hasValue) {
xml.startChildren()
writeNode(ret.value!!)
}
xml.endElt()
}
private fun write(incdec: PtPostIncrDecr) {
if(incdec.operator=="++") xml.elt("inc") else xml.elt("dec")
xml.startChildren()
write(incdec.target)
xml.endElt()
}
private fun write(label: PtLabel) {
xml.elt("label")
xml.attr("name", label.scopedName.joinToString("."))
xml.pos(label.position)
xml.endElt()
}
private fun write(block: PtBlock) {
xml.elt("block")
xml.attr("name", block.scopedName.joinToString("."))
if(block.address!=null)
xml.attr("address", block.address!!.toString())
xml.attr("library", block.library.toString())
xml.pos(block.position)
xml.startChildren()
block.children.forEach { writeNode(it) }
xml.endElt()
}
private fun write(memMapped: PtMemMapped) {
xml.writeTextNode("memvar",
listOf(
Pair("name", memMapped.scopedName.joinToString(".")),
Pair("type", memMapped.type.name)
),
memMapped.address.toString(),
false)
}
private fun write(target: PtAssignTarget) {
xml.elt("target")
xml.startChildren()
if(target.identifier!=null) {
writeNode(target.identifier!!)
} else if(target.memory!=null) {
writeNode(target.memory!!)
} else if(target.array!=null) {
writeNode(target.array!!)
} else
throw InternalCompilerException("weird assign target")
xml.endElt()
}
private fun write(jump: PtJump) {
xml.elt("jump")
if(jump.identifier!=null) xml.attr("symbol", strTargetName(jump.identifier!!))
else if(jump.address!=null) xml.attr("address", jump.address!!.toString())
else if(jump.generatedLabel!=null) xml.attr("label", jump.generatedLabel!!)
else
throw InternalCompilerException("weird jump target")
xml.endElt()
}
private fun write(sub: PtSub) {
xml.elt("sub")
xml.attr("name", sub.scopedName.joinToString("."))
if(sub.inline)
xml.attr("inline", "true")
xml.attr("returntype", sub.returntype?.toString() ?: "VOID")
xml.pos(sub.position)
xml.startChildren()
if(sub.parameters.isNotEmpty()) {
xml.elt("parameters")
xml.startChildren()
sub.parameters.forEach { write(it) }
xml.endElt()
}
sub.children.forEach { writeNode(it) }
xml.endElt()
}
private fun write(parameter: PtSubroutineParameter, registerOrStatusflag: RegisterOrStatusflag? = null) {
xml.elt("param")
xml.attr("name", parameter.name)
xml.attr("type", parameter.type.name)
if(registerOrStatusflag?.statusflag!=null) {
xml.attr("statusflag", registerOrStatusflag.statusflag!!.toString())
}
if(registerOrStatusflag?.registerOrPair!=null){
xml.attr("registers", registerOrStatusflag.registerOrPair!!.name)
}
xml.endElt()
}
private fun write(asmSub: PtAsmSub) {
if(asmSub.address!=null) {
xml.elt("romsub")
xml.attr("name", asmSub.scopedName.joinToString("."))
xml.attr("address", asmSub.address!!.toString())
if(asmSub.inline)
xml.attr("inline", "true")
xml.pos(asmSub.position)
xml.startChildren()
paramsEtcetera(asmSub)
xml.endElt()
}
else {
xml.elt("asmsub")
xml.attr("name", asmSub.scopedName.joinToString("."))
if(asmSub.inline)
xml.attr("inline", "true")
xml.pos(asmSub.position)
xml.startChildren()
paramsEtcetera(asmSub)
xml.elt("code")
xml.startChildren()
asmSub.children.forEach { writeNode(it) }
xml.endElt()
xml.endElt()
}
}
private fun paramsEtcetera(asmSub: PtAsmSub) {
if(asmSub.parameters.isNotEmpty()) {
xml.elt("parameters")
xml.startChildren()
asmSub.parameters.forEach { (param, reg) -> write(param, reg) }
xml.endElt()
}
if(asmSub.clobbers.isNotEmpty()) {
xml.elt("clobbers")
xml.attr("registers", asmSub.clobbers.joinToString(",") { it.name })
xml.endElt()
}
if(asmSub.retvalRegisters.isNotEmpty()) {
xml.elt("returns")
xml.startChildren()
asmSub.retvalRegisters.forEach {
xml.elt("register")
if(it.statusflag!=null)
xml.attr("statusflag", it.statusflag!!.toString())
if(it.registerOrPair!=null)
xml.attr("registers", it.registerOrPair!!.toString())
xml.endElt()
}
xml.endElt()
}
}
private fun write(constant: PtConstant) {
xml.writeTextNode("const",
listOf(
Pair("name", constant.scopedName.joinToString(".")),
Pair("type", constant.type.name)
),
intOrDouble(constant.type, constant.value).toString(), false)
}
private fun write(variable: PtVariable) {
// the variable declaration nodes are still present in the Ast,
// but the Symboltable should be used look up their details.
xml.elt("vardecl")
xml.attr("name", variable.scopedName.joinToString("."))
xml.attr("type", variable.type.name)
if(variable.arraySize!=null)
xml.attr("arraysize", variable.arraySize.toString())
if(variable.value!=null) {
// static initialization value
xml.startChildren()
writeNode(variable.value!!)
}
xml.endElt()
}
private fun strTargetName(ident: PtIdentifier): String = ident.targetName.joinToString(".")
private fun strTargetName(call: PtFunctionCall): String = call.functionName.joinToString(".")
private fun intOrDouble(type: DataType, value: Double): Number =
if(type in IntegerDatatypes) value.toInt() else value
}

View File

@ -0,0 +1,89 @@
package prog8.codegen.experimental
import prog8.code.core.Position
import java.util.*
import javax.xml.stream.XMLStreamWriter
class IndentingXmlWriter(val xml: XMLStreamWriter): XMLStreamWriter by xml {
private var indent = 0
private var content = Stack<Boolean>()
fun doc(version: String? = null) = if(version==null) writeStartDocument() else writeStartDocument(version)
fun endDoc() = writeEndDocument()
fun elt(name: String) = writeStartElement(name)
fun attr(name: String, value: String) = writeAttribute(name, value)
fun attrs(attributes: List<Pair<String, String>>) = attributes.forEach { writeAttribute(it.first, it.second) }
fun startChildren() {
xml.writeCharacters("\n")
content.pop()
content.push(true)
}
fun endElt(writeIndent: Boolean=true) = writeEndElement(writeIndent)
fun pos(pos: Position) = writeAttribute("src", pos.toString())
fun comment(text: String) {
writeComment(text)
writeCharacters("\n")
}
override fun writeStartDocument() {
xml.writeStartDocument()
xml.writeCharacters("\n")
content.push(true)
}
override fun writeStartDocument(version: String) {
xml.writeStartDocument(version)
xml.writeCharacters("\n")
content.push(true)
}
override fun writeEndDocument() {
xml.writeEndDocument()
xml.writeCharacters("\n")
require(indent==0)
require(content.size==1)
}
override fun writeStartElement(name: String) {
xml.writeCharacters(" ".repeat(indent))
xml.writeStartElement(name)
indent++
content.push(false)
}
override fun writeStartElement(name: String, ns: String) {
xml.writeCharacters(" ".repeat(indent))
xml.writeStartElement(name, ns)
indent++
content.push(false)
}
fun writeEndElement(writeIndents: Boolean) {
indent--
if(content.pop() && writeIndents)
xml.writeCharacters(" ".repeat(indent))
xml.writeEndElement()
xml.writeCharacters("\n")
}
override fun writeEndElement() = writeEndElement(true)
override fun writeStartElement(name: String, ns: String, p2: String) {
xml.writeCharacters(" ".repeat(indent))
xml.writeStartElement(name, ns, p2)
indent++
content.push(false)
}
fun writeTextNode(name: String, attrs: List<Pair<String, String>>, text: String, cdata: Boolean = true) {
xml.writeCharacters(" ".repeat(indent))
xml.writeStartElement(name)
attrs.forEach { (name, value) -> xml.writeAttribute(name, value) }
if(cdata)
xml.writeCData(text)
else
xml.writeCharacters(text)
xml.writeEndElement()
xml.writeCharacters("\n")
}
}

View File

@ -0,0 +1,47 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':virtualmachine')
implementation project(':codeAst')
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.14"
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
}
// note: there are no unit tests in this module!

View File

@ -0,0 +1,16 @@
<?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$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="codeAst" />
<orderEntry type="module" module-name="codeCore" />
<orderEntry type="module" module-name="virtualmachine" />
</component>
</module>

View File

@ -0,0 +1,138 @@
package prog8.codegen.virtual
import prog8.code.core.AssemblyError
import prog8.code.core.CompilationOptions
import prog8.code.core.IAssemblyProgram
import prog8.vm.Instruction
import prog8.vm.Opcode
import prog8.vm.OpcodesWithAddress
import prog8.vm.VmDataType
import java.io.BufferedWriter
import java.nio.file.Path
import kotlin.io.path.bufferedWriter
import kotlin.io.path.div
internal class AssemblyProgram(override val name: String,
private val allocations: VariableAllocator
) : IAssemblyProgram {
private val globalInits = mutableListOf<VmCodeLine>()
private val blocks = mutableListOf<VmCodeChunk>()
override fun assemble(options: CompilationOptions): Boolean {
val outfile = options.outputDir / ("$name.p8virt")
println("write code to $outfile")
outfile.bufferedWriter().use { out ->
allocations.asVmMemory().forEach { (name, alloc) ->
out.write("; ${name.joinToString(".")}\n")
out.write(alloc + "\n")
}
out.write("------PROGRAM------\n")
if(!options.dontReinitGlobals) {
out.write("; global var inits\n")
globalInits.forEach { out.writeLine(it) }
}
out.write("; actual program code\n")
blocks.asSequence().flatMap { it.lines }.forEach { line->out.writeLine(line) }
}
return true
}
private fun BufferedWriter.writeLine(line: VmCodeLine) {
when(line) {
is VmCodeComment -> write("; ${line.comment}\n")
is VmCodeInstruction -> {
write(line.ins.toString() + "\n")
}
is VmCodeLabel -> write("_" + line.name.joinToString(".") + ":\n")
is VmCodeInlineAsm -> {
val asm = line.assembly.replace("""\{[a-zA-Z\d_\.]+\}""".toRegex()) { matchResult ->
val name = matchResult.value.substring(1, matchResult.value.length-1).split('.')
allocations.get(name).toString() }
write(asm+"\n")
}
is VmCodeInlineBinary -> {
write("incbin \"${line.file}\"")
if(line.offset!=null)
write(",${line.offset}")
if(line.length!=null)
write(",${line.length}")
write("\n")
}
else -> throw AssemblyError("invalid vm code line")
}
}
fun addGlobalInits(chunk: VmCodeChunk) = globalInits.addAll(chunk.lines)
fun addBlock(block: VmCodeChunk) = blocks.add(block)
}
internal sealed class VmCodeLine
internal class VmCodeInstruction(
opcode: Opcode,
type: VmDataType?=null,
reg1: Int?=null, // 0-$ffff
reg2: Int?=null, // 0-$ffff
fpReg1: Int?=null, // 0-$ffff
fpReg2: Int?=null, // 0-$ffff
value: Int?=null, // 0-$ffff
fpValue: Float?=null,
labelSymbol: List<String>?=null // alternative to value for branch/call/jump labels
): VmCodeLine() {
val ins = Instruction(opcode, type, reg1, reg2, fpReg1, fpReg2, value, fpValue, labelSymbol)
init {
if(reg1!=null && (reg1<0 || reg1>65536))
throw IllegalArgumentException("reg1 out of bounds")
if(reg2!=null && (reg2<0 || reg2>65536))
throw IllegalArgumentException("reg2 out of bounds")
if(fpReg1!=null && (fpReg1<0 || fpReg1>65536))
throw IllegalArgumentException("fpReg1 out of bounds")
if(fpReg2!=null && (fpReg2<0 || fpReg2>65536))
throw IllegalArgumentException("fpReg2 out of bounds")
if(value!=null && opcode !in OpcodesWithAddress) {
when (type) {
VmDataType.BYTE -> {
if (value < -128 || value > 255)
throw IllegalArgumentException("value out of range for byte: $value")
}
VmDataType.WORD -> {
if (value < -32768 || value > 65535)
throw IllegalArgumentException("value out of range for word: $value")
}
VmDataType.FLOAT, null -> {}
}
}
}
}
internal class VmCodeLabel(val name: List<String>): VmCodeLine()
internal class VmCodeComment(val comment: String): VmCodeLine()
internal class VmCodeChunk(initial: VmCodeLine? = null) {
val lines = mutableListOf<VmCodeLine>()
init {
if(initial!=null)
lines.add(initial)
}
operator fun plusAssign(line: VmCodeLine) {
lines.add(line)
}
operator fun plusAssign(chunk: VmCodeChunk) {
lines.addAll(chunk.lines)
}
}
internal class VmCodeInlineAsm(asm: String): VmCodeLine() {
val assembly: String = asm.trimIndent()
}
internal class VmCodeInlineBinary(val file: Path, val offset: UInt?, val length: UInt?): VmCodeLine()

View File

@ -0,0 +1,232 @@
package prog8.codegen.virtual
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.code.core.SignedDatatypes
import prog8.vm.Opcode
import prog8.vm.VmDataType
internal class AssignmentGen(private val codeGen: CodeGen, private val expressionEval: ExpressionGen) {
internal fun translate(assignment: PtAssignment): VmCodeChunk {
if(assignment.target.children.single() is PtMachineRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
return if (assignment.isInplaceAssign)
translateInplaceAssign(assignment)
else
translateRegularAssign(assignment)
}
private fun translateInplaceAssign(assignment: PtAssignment): VmCodeChunk {
val ident = assignment.target.identifier
val memory = assignment.target.memory
val array = assignment.target.array
return if(ident!=null) {
val address = codeGen.allocations.get(ident.targetName)
assignSelfInMemory(address, assignment.value, assignment)
} else if(memory != null) {
if(memory.address is PtNumber)
assignSelfInMemory((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
else
fallbackAssign(assignment)
} else if(array!=null) {
// TODO in-place array element assignment?
fallbackAssign(assignment)
} else {
fallbackAssign(assignment)
}
}
private fun assignSelfInMemory(
address: Int,
value: PtExpression,
origAssign: PtAssignment
): VmCodeChunk {
val vmDt = codeGen.vmType(value.type)
val code = VmCodeChunk()
when(value) {
is PtIdentifier -> return code // do nothing, x=x null assignment.
is PtMachineRegister -> return code // do nothing, reg=reg null assignment
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address)
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, origAssign)
is PtMemoryByte -> {
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
code // do nothing, mem=mem null assignment.
else {
// read and write a (i/o) memory location to itself.
val tempReg = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
code
}
}
else -> return fallbackAssign(origAssign)
}
}
private fun fallbackAssign(origAssign: PtAssignment): VmCodeChunk {
if (codeGen.options.slowCodegenWarnings)
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
return translateRegularAssign(origAssign)
}
private fun inplaceBinexpr(
operator: String,
operand: PtExpression,
vmDt: VmDataType,
signed: Boolean,
address: Int,
origAssign: PtAssignment
): VmCodeChunk {
when(operator) {
"+" -> return expressionEval.operatorPlusInplace(address, vmDt, operand)
"-" -> return expressionEval.operatorMinusInplace(address, vmDt, operand)
"*" -> return expressionEval.operatorMultiplyInplace(address, vmDt, operand)
"/" -> return expressionEval.operatorDivideInplace(address, vmDt, signed, operand)
"|" -> return expressionEval.operatorOrInplace(address, vmDt, operand)
"&" -> return expressionEval.operatorAndInplace(address, vmDt, operand)
"^" -> return expressionEval.operatorXorInplace(address, vmDt, operand)
"<<" -> return expressionEval.operatorShiftLeftInplace(address, vmDt, operand)
">>" -> return expressionEval.operatorShiftRightInplace(address, vmDt, signed, operand)
else -> {}
}
return fallbackAssign(origAssign)
}
private fun inplacePrefix(operator: String, vmDt: VmDataType, address: Int): VmCodeChunk {
val code= VmCodeChunk()
when(operator) {
"+" -> { }
"-" -> {
code += VmCodeInstruction(Opcode.NEGM, vmDt, value = address)
}
"~" -> {
val regMask = codeGen.vmRegisters.nextFree()
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
code += VmCodeInstruction(Opcode.XORM, vmDt, reg1=regMask, value = address)
}
"not" -> {
code += VmCodeInstruction(Opcode.NOTM, vmDt, value = address)
}
else -> throw AssemblyError("weird prefix operator")
}
return code
}
private fun translateRegularAssign(assignment: PtAssignment): VmCodeChunk {
// note: assigning array and string values is done via an explicit memcopy/stringcopy function call.
val ident = assignment.target.identifier
val memory = assignment.target.memory
val array = assignment.target.array
val vmDt = codeGen.vmType(assignment.value.type)
val code = VmCodeChunk()
var resultRegister = -1
var resultFpRegister = -1
val zero = codeGen.isZero(assignment.value)
if(!zero) {
// calculate the assignment value
if (vmDt == VmDataType.FLOAT) {
resultFpRegister = codeGen.vmRegisters.nextFreeFloat()
code += expressionEval.translateExpression(assignment.value, -1, resultFpRegister)
} else {
resultRegister = if (assignment.value is PtMachineRegister) {
(assignment.value as PtMachineRegister).register
} else {
val reg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(assignment.value, reg, -1)
reg
}
}
}
if(ident!=null) {
val address = codeGen.allocations.get(ident.targetName)
code += if(zero) {
VmCodeInstruction(Opcode.STOREZM, vmDt, value = address)
} else {
if (vmDt == VmDataType.FLOAT)
VmCodeInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, value = address)
else
VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, value = address)
}
}
else if(array!=null) {
val variable = array.variable.targetName
var variableAddr = codeGen.allocations.get(variable)
val itemsize = codeGen.program.memsizer.memorySize(array.type)
val fixedIndex = constIntValue(array.index)
if(zero) {
if(fixedIndex!=null) {
variableAddr += fixedIndex*itemsize
code += VmCodeInstruction(Opcode.STOREZM, vmDt, value=variableAddr)
} else {
val indexReg = codeGen.vmRegisters.nextFree()
code += loadIndexReg(array, itemsize, indexReg)
code += VmCodeInstruction(Opcode.STOREZX, vmDt, reg1=indexReg, value=variableAddr)
}
} else {
if(vmDt== VmDataType.FLOAT) {
if(fixedIndex!=null) {
variableAddr += fixedIndex*itemsize
code += VmCodeInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, value=variableAddr)
} else {
val indexReg = codeGen.vmRegisters.nextFree()
code += loadIndexReg(array, itemsize, indexReg)
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, value=variableAddr)
}
} else {
if(fixedIndex!=null) {
variableAddr += fixedIndex*itemsize
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, value=variableAddr)
} else {
val indexReg = codeGen.vmRegisters.nextFree()
code += loadIndexReg(array, itemsize, indexReg)
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1 = resultRegister, reg2=indexReg, value=variableAddr)
}
}
}
}
else if(memory!=null) {
require(vmDt== VmDataType.BYTE)
if(zero) {
if(memory.address is PtNumber) {
code += VmCodeInstruction(Opcode.STOREZM, vmDt, value=(memory.address as PtNumber).number.toInt())
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(memory.address, addressReg, -1)
code += VmCodeInstruction(Opcode.STOREZI, vmDt, reg1=addressReg)
}
} else {
if(memory.address is PtNumber) {
code += VmCodeInstruction(Opcode.STOREM, vmDt, reg1=resultRegister, value=(memory.address as PtNumber).number.toInt())
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += expressionEval.translateExpression(memory.address, addressReg, -1)
code += VmCodeInstruction(Opcode.STOREI, vmDt, reg1=resultRegister, reg2=addressReg)
}
}
}
else
throw AssemblyError("weird assigntarget")
return code
}
private fun loadIndexReg(array: PtArrayIndexer, itemsize: Int, indexReg: Int): VmCodeChunk {
val code = VmCodeChunk()
if(itemsize==1) {
code += expressionEval.translateExpression(array.index, indexReg, -1)
}
else {
val mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
mult.children += array.index
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
code += expressionEval.translateExpression(mult, indexReg, -1)
}
return code
}
}

View File

@ -0,0 +1,390 @@
package prog8.codegen.virtual
import prog8.code.StStaticVariable
import prog8.code.ast.*
import prog8.code.core.AssemblyError
import prog8.code.core.DataType
import prog8.vm.Opcode
import prog8.vm.Syscall
import prog8.vm.VmDataType
internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen: ExpressionGen) {
fun translate(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
return when(call.name) {
"any" -> funcAny(call, resultRegister)
"all" -> funcAll(call, resultRegister)
"abs" -> funcAbs(call, resultRegister)
"cmp" -> funcCmp(call)
"sgn" -> funcSgn(call, resultRegister)
"sqrt16" -> funcSqrt16(call, resultRegister)
"pop" -> funcPop(call)
"popw" -> funcPopw(call)
"push" -> funcPush(call)
"pushw" -> funcPushw(call)
"rsave",
"rsavex",
"rrestore",
"rrestorex" -> VmCodeChunk() // vm doesn't have registers to save/restore
"rnd" -> funcRnd(resultRegister)
"rndw" -> funcRndw(resultRegister)
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
"msb" -> funcMsb(call, resultRegister)
"lsb" -> funcLsb(call, resultRegister)
"memory" -> funcMemory(call, resultRegister)
"peek" -> funcPeek(call, resultRegister)
"peekw" -> funcPeekW(call, resultRegister)
"poke" -> funcPoke(call)
"pokew" -> funcPokeW(call)
"pokemon" -> VmCodeChunk()
"mkword" -> funcMkword(call, resultRegister)
"sort" -> funcSort(call)
"reverse" -> funcReverse(call)
"swap" -> funcSwap(call)
"rol" -> funcRolRor2(Opcode.ROXL, call, resultRegister)
"ror" -> funcRolRor2(Opcode.ROXR, call, resultRegister)
"rol2" -> funcRolRor2(Opcode.ROL, call, resultRegister)
"ror2" -> funcRolRor2(Opcode.ROR, call, resultRegister)
else -> TODO("builtinfunc ${call.name}")
}
}
private fun funcCmp(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
val leftRegister = codeGen.vmRegisters.nextFree()
val rightRegister = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], leftRegister, -1)
code += exprGen.translateExpression(call.args[1], rightRegister, -1)
code += VmCodeInstruction(Opcode.CMP, codeGen.vmType(call.args[0].type), reg1=leftRegister, reg2=rightRegister)
return code
}
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val code = VmCodeChunk()
val syscall =
when (array.dt) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> Syscall.ANY_BYTE
DataType.ARRAY_UW,
DataType.ARRAY_W -> Syscall.ANY_WORD
DataType.ARRAY_F -> Syscall.ANY_FLOAT
else -> throw IllegalArgumentException("weird type")
}
code += exprGen.translateExpression(call.args[0], 0, -1)
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1 = 1, value = array.length)
code += VmCodeInstruction(Opcode.SYSCALL, value = syscall.ordinal)
if (resultRegister != 0)
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1 = resultRegister, reg2 = 0)
return code
}
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val syscall =
when(array.dt) {
DataType.ARRAY_UB,
DataType.ARRAY_B -> Syscall.ALL_BYTE
DataType.ARRAY_UW,
DataType.ARRAY_W -> Syscall.ALL_WORD
DataType.ARRAY_F -> Syscall.ALL_FLOAT
else -> throw IllegalArgumentException("weird type")
}
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args[0], 0, -1)
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
code += VmCodeInstruction(Opcode.SYSCALL, value=syscall.ordinal)
if(resultRegister!=0)
code += VmCodeInstruction(Opcode.LOADR, VmDataType.BYTE, reg1=resultRegister, reg2=0)
return code
}
private fun funcAbs(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
val sourceDt = call.args.single().type
if(sourceDt!=DataType.UWORD) {
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
when (sourceDt) {
DataType.UBYTE -> {
code += VmCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
}
DataType.BYTE -> {
val andReg = codeGen.vmRegisters.nextFree()
val notNegativeLabel = codeGen.createLabelName()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=andReg, value=0x80)
code += VmCodeInstruction(Opcode.AND, VmDataType.BYTE, reg1=andReg, reg2=resultRegister)
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=andReg, labelSymbol = notNegativeLabel)
code += VmCodeInstruction(Opcode.NEG, VmDataType.BYTE, reg1=resultRegister)
code += VmCodeInstruction(Opcode.EXT, VmDataType.BYTE, reg1=resultRegister)
code += VmCodeLabel(notNegativeLabel)
}
DataType.WORD -> {
val andReg = codeGen.vmRegisters.nextFree()
val notNegativeLabel = codeGen.createLabelName()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.WORD, reg1=andReg, value=0x8000)
code += VmCodeInstruction(Opcode.AND, VmDataType.WORD, reg1=andReg, reg2=resultRegister)
code += VmCodeInstruction(Opcode.BZ, VmDataType.WORD, reg1=andReg, labelSymbol = notNegativeLabel)
code += VmCodeInstruction(Opcode.NEG, VmDataType.WORD, reg1=resultRegister)
code += VmCodeLabel(notNegativeLabel)
}
else -> throw AssemblyError("weird type")
}
}
return code
}
private fun funcSgn(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += VmCodeInstruction(Opcode.SGN, codeGen.vmType(call.type), reg1=resultRegister, reg2=reg)
return code
}
private fun funcSqrt16(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += VmCodeInstruction(Opcode.SQRT, VmDataType.WORD, reg1=resultRegister, reg2=reg)
return code
}
private fun funcPop(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
val reg = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.POP, VmDataType.BYTE, reg1=reg)
code += assignRegisterTo(call.args.single(), reg)
return code
}
private fun funcPopw(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
val reg = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.POP, VmDataType.WORD, reg1=reg)
code += assignRegisterTo(call.args.single(), reg)
return code
}
private fun funcPush(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += VmCodeInstruction(Opcode.PUSH, VmDataType.BYTE, reg1=reg)
return code
}
private fun funcPushw(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
val reg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), reg, -1)
code += VmCodeInstruction(Opcode.PUSH, VmDataType.WORD, reg1=reg)
return code
}
private fun funcSwap(call: PtBuiltinFunctionCall): VmCodeChunk {
val left = call.args[0]
val right = call.args[1]
val leftReg = codeGen.vmRegisters.nextFree()
val rightReg = codeGen.vmRegisters.nextFree()
val code = VmCodeChunk()
code += exprGen.translateExpression(left, leftReg, -1)
code += exprGen.translateExpression(right, rightReg, -1)
code += assignRegisterTo(left, rightReg)
code += assignRegisterTo(right, leftReg)
return code
}
private fun funcReverse(call: PtBuiltinFunctionCall): VmCodeChunk {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val sortSyscall =
when(array.dt) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> Syscall.REVERSE_BYTES
DataType.ARRAY_UW, DataType.ARRAY_W -> Syscall.REVERSE_WORDS
DataType.ARRAY_F -> Syscall.REVERSE_FLOATS
else -> throw IllegalArgumentException("weird type to reverse")
}
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args[0], 0, -1)
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
code += VmCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal)
return code
}
private fun funcSort(call: PtBuiltinFunctionCall): VmCodeChunk {
val arrayName = call.args[0] as PtIdentifier
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
val sortSyscall =
when(array.dt) {
DataType.ARRAY_UB -> Syscall.SORT_UBYTE
DataType.ARRAY_B -> Syscall.SORT_BYTE
DataType.ARRAY_UW -> Syscall.SORT_UWORD
DataType.ARRAY_W -> Syscall.SORT_WORD
DataType.STR -> Syscall.SORT_UBYTE
DataType.ARRAY_F -> throw java.lang.IllegalArgumentException("sorting a floating point array is not supported")
else -> throw IllegalArgumentException("weird type to sort")
}
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args[0], 0, -1)
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=1, value=array.length)
code += VmCodeInstruction(Opcode.SYSCALL, value=sortSyscall.ordinal)
return code
}
private fun funcMkword(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val msbReg = codeGen.vmRegisters.nextFree()
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args[0], msbReg, -1)
code += exprGen.translateExpression(call.args[1], resultRegister, -1)
code += VmCodeInstruction(Opcode.CONCAT, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg)
return code
}
private fun funcPokeW(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.WORD, value = address)
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += VmCodeInstruction(Opcode.STOREZI, VmDataType.WORD, reg2 = addressReg)
}
} else {
val valueReg = codeGen.vmRegisters.nextFree()
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += VmCodeInstruction(Opcode.STOREM, VmDataType.WORD, reg1 = valueReg, value = address)
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += VmCodeInstruction(Opcode.STOREI, VmDataType.WORD, reg1 = valueReg, reg2 = addressReg)
}
}
return code
}
private fun funcPoke(call: PtBuiltinFunctionCall): VmCodeChunk {
val code = VmCodeChunk()
if(codeGen.isZero(call.args[1])) {
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.BYTE, value = address)
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += VmCodeInstruction(Opcode.STOREZI, VmDataType.BYTE, reg2 = addressReg)
}
} else {
val valueReg = codeGen.vmRegisters.nextFree()
if (call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += VmCodeInstruction(Opcode.STOREM, VmDataType.BYTE, reg1 = valueReg, value = address)
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args[0], addressReg, -1)
code += exprGen.translateExpression(call.args[1], valueReg, -1)
code += VmCodeInstruction(Opcode.STOREI, VmDataType.BYTE, reg1 = valueReg, reg2 = addressReg)
}
}
return code
}
private fun funcPeekW(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
if(call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += VmCodeInstruction(Opcode.LOADM, VmDataType.WORD, reg1 = resultRegister, value = address)
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
code += VmCodeInstruction(Opcode.LOADI, VmDataType.WORD, reg1 = resultRegister, reg2 = addressReg)
}
return code
}
private fun funcPeek(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
if(call.args[0] is PtNumber) {
val address = (call.args[0] as PtNumber).number.toInt()
code += VmCodeInstruction(Opcode.LOADM, VmDataType.BYTE, reg1 = resultRegister, value = address)
} else {
val addressReg = codeGen.vmRegisters.nextFree()
code += exprGen.translateExpression(call.args.single(), addressReg, -1)
code += VmCodeInstruction(Opcode.LOADI, VmDataType.BYTE, reg1 = resultRegister, reg2 = addressReg)
}
return code
}
private fun funcRnd(resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
code += VmCodeInstruction(Opcode.RND, VmDataType.BYTE, reg1=resultRegister)
return code
}
private fun funcRndw(resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
code += VmCodeInstruction(Opcode.RND, VmDataType.WORD, reg1=resultRegister)
return code
}
private fun funcMemory(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val name = (call.args[0] as PtString).value
val size = (call.args[1] as PtNumber).number.toUInt()
val align = (call.args[2] as PtNumber).number.toUInt()
val existing = codeGen.allocations.getMemorySlab(name)
val address = if(existing==null)
codeGen.allocations.allocateMemorySlab(name, size, align)
else if(existing.second!=size || existing.third!=align) {
codeGen.errors.err("memory slab '$name' already exists with a different size or alignment", call.position)
return VmCodeChunk()
}
else
existing.first
val code = VmCodeChunk()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.WORD, reg1=resultRegister, value=address.toInt())
return code
}
private fun funcLsb(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return code
}
private fun funcMsb(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args.single(), resultRegister, -1)
code += VmCodeInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = resultRegister, reg2=resultRegister)
// note: if a word result is needed, the upper byte is cleared by the typecast that follows. No need to do it here.
return code
}
private fun funcRolRor2(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk {
val vmDt = codeGen.vmType(call.args[0].type)
val code = VmCodeChunk()
code += exprGen.translateExpression(call.args[0], resultRegister, -1)
code += VmCodeInstruction(opcode, vmDt, reg1=resultRegister)
code += assignRegisterTo(call.args[0], resultRegister)
return code
}
private fun assignRegisterTo(target: PtExpression, register: Int): VmCodeChunk {
val code = VmCodeChunk()
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(target.position)
assignTarget.children.add(target)
assignment.children.add(assignTarget)
assignment.children.add(PtMachineRegister(register, target.type, target.position))
code += codeGen.translateNode(assignment)
return code
}
}

View File

@ -0,0 +1,832 @@
package prog8.codegen.virtual
import prog8.code.StStaticVariable
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
import prog8.vm.Opcode
import prog8.vm.VmDataType
import kotlin.math.pow
internal class VmRegisterPool {
private var firstFree: Int=3 // integer registers 0,1,2 are reserved
private var firstFreeFloat: Int=0
fun peekNext() = firstFree
fun peekNextFloat() = firstFreeFloat
fun nextFree(): Int {
val result = firstFree
firstFree++
if(firstFree>65535)
throw AssemblyError("out of virtual registers (int)")
return result
}
fun nextFreeFloat(): Int {
val result = firstFreeFloat
firstFreeFloat++
if(firstFreeFloat>65535)
throw AssemblyError("out of virtual registers (fp)")
return result
}
}
class CodeGen(internal val program: PtProgram,
internal val symbolTable: SymbolTable,
internal val options: CompilationOptions,
internal val errors: IErrorReporter
): IAssemblyGenerator {
internal val allocations = VariableAllocator(symbolTable, program, errors)
private val expressionEval = ExpressionGen(this)
private val builtinFuncGen = BuiltinFuncGen(this, expressionEval)
private val assignmentGen = AssignmentGen(this, expressionEval)
internal val vmRegisters = VmRegisterPool()
override fun compileToAssembly(): IAssemblyProgram? {
val vmprog = AssemblyProgram(program.name, allocations)
if(!options.dontReinitGlobals) {
// collect global variables initializers
program.allBlocks().forEach {
val code = VmCodeChunk()
it.children.filterIsInstance<PtAssignment>().forEach { assign -> code += assignmentGen.translate(assign) }
vmprog.addGlobalInits(code)
}
}
for (block in program.allBlocks()) {
vmprog.addBlock(translate(block))
}
println("Vm codegen: amount of vm registers=${vmRegisters.peekNext()}")
return vmprog
}
internal fun translateNode(node: PtNode): VmCodeChunk {
val code = when(node) {
is PtBlock -> translate(node)
is PtSub -> translate(node)
is PtScopeVarsDecls -> VmCodeChunk() // vars should be looked up via symbol table
is PtVariable -> VmCodeChunk() // var should be looked up via symbol table
is PtMemMapped -> VmCodeChunk() // memmapped var should be looked up via symbol table
is PtConstant -> VmCodeChunk() // constants have all been folded into the code
is PtAssignment -> assignmentGen.translate(node)
is PtNodeGroup -> translateGroup(node.children)
is PtBuiltinFunctionCall -> translateBuiltinFunc(node, 0)
is PtFunctionCall -> expressionEval.translate(node, 0, 0)
is PtNop -> VmCodeChunk()
is PtReturn -> translate(node)
is PtJump -> translate(node)
is PtWhen -> translate(node)
is PtPipe -> expressionEval.translate(node, 0)
is PtForLoop -> translate(node)
is PtIfElse -> translate(node)
is PtPostIncrDecr -> translate(node)
is PtRepeatLoop -> translate(node)
is PtLabel -> VmCodeChunk(VmCodeLabel(node.scopedName))
is PtBreakpoint -> VmCodeChunk(VmCodeInstruction(Opcode.BREAKPOINT))
is PtConditionalBranch -> translate(node)
is PtInlineAssembly -> VmCodeChunk(VmCodeInlineAsm(node.assembly))
is PtIncludeBinary -> VmCodeChunk(VmCodeInlineBinary(node.file, node.offset, node.length))
is PtAsmSub -> TODO("asmsub not yet supported on virtual machine target ${node.position}")
is PtAddressOf,
is PtContainmentCheck,
is PtMemoryByte,
is PtProgram,
is PtArrayIndexer,
is PtBinaryExpression,
is PtIdentifier,
is PtWhenChoice,
is PtPrefix,
is PtRange,
is PtAssignTarget,
is PtTypeCast,
is PtSubroutineParameter,
is PtNumber,
is PtArray,
is PtString -> throw AssemblyError("should not occur as separate statement node ${node.position}")
else -> TODO("missing codegen for $node")
}
if(code.lines.isNotEmpty() && node.position.line!=0)
code.lines.add(0, VmCodeComment(node.position.toString()))
return code
}
private fun translate(branch: PtConditionalBranch): VmCodeChunk {
val code = VmCodeChunk()
val elseLabel = createLabelName()
// note that the branch opcode used is the opposite as the branch condition, because the generated code jumps to the 'else' part
code += when(branch.condition) {
BranchCondition.CS -> VmCodeInstruction(Opcode.BSTCC, labelSymbol = elseLabel)
BranchCondition.CC -> VmCodeInstruction(Opcode.BSTCS, labelSymbol = elseLabel)
BranchCondition.EQ, BranchCondition.Z -> VmCodeInstruction(Opcode.BSTNE, labelSymbol = elseLabel)
BranchCondition.NE, BranchCondition.NZ -> VmCodeInstruction(Opcode.BSTEQ, labelSymbol = elseLabel)
BranchCondition.MI, BranchCondition.NEG -> VmCodeInstruction(Opcode.BSTPOS, labelSymbol = elseLabel)
BranchCondition.PL, BranchCondition.POS -> VmCodeInstruction(Opcode.BSTNEG, labelSymbol = elseLabel)
BranchCondition.VC,
BranchCondition.VS -> throw AssemblyError("conditional branch ${branch.condition} not supported in vm target due to lack of cpu V flag ${branch.position}")
}
code += translateNode(branch.trueScope)
if(branch.falseScope.children.isNotEmpty()) {
val endLabel = createLabelName()
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = endLabel)
code += VmCodeLabel(elseLabel)
code += translateNode(branch.falseScope)
code += VmCodeLabel(endLabel)
} else {
code += VmCodeLabel(elseLabel)
}
return code
}
private fun translate(whenStmt: PtWhen): VmCodeChunk {
if(whenStmt.choices.children.isEmpty())
return VmCodeChunk()
val code = VmCodeChunk()
val valueReg = vmRegisters.nextFree()
val choiceReg = vmRegisters.nextFree()
val valueDt = vmType(whenStmt.value.type)
code += expressionEval.translateExpression(whenStmt.value, valueReg, -1)
val choices = whenStmt.choices.children.map {it as PtWhenChoice }
val endLabel = createLabelName()
for (choice in choices) {
if(choice.isElse) {
code += translateNode(choice.statements)
} else {
val skipLabel = createLabelName()
val values = choice.values.children.map {it as PtNumber}
if(values.size==1) {
code += VmCodeInstruction(Opcode.LOAD, valueDt, reg1=choiceReg, value=values[0].number.toInt())
code += VmCodeInstruction(Opcode.BNE, valueDt, reg1=valueReg, reg2=choiceReg, labelSymbol = skipLabel)
code += translateNode(choice.statements)
if(choice.statements.children.last() !is PtReturn)
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = endLabel)
} else {
val matchLabel = createLabelName()
for (value in values) {
code += VmCodeInstruction(Opcode.LOAD, valueDt, reg1=choiceReg, value=value.number.toInt())
code += VmCodeInstruction(Opcode.BEQ, valueDt, reg1=valueReg, reg2=choiceReg, labelSymbol = matchLabel)
}
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = skipLabel)
code += VmCodeLabel(matchLabel)
code += translateNode(choice.statements)
if(choice.statements.children.last() !is PtReturn)
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = endLabel)
}
code += VmCodeLabel(skipLabel)
}
}
code += VmCodeLabel(endLabel)
return code
}
private fun translate(forLoop: PtForLoop): VmCodeChunk {
val loopvar = symbolTable.lookup(forLoop.variable.targetName) as StStaticVariable
val iterable = forLoop.iterable
val code = VmCodeChunk()
when(iterable) {
is PtRange -> {
if(iterable.from is PtNumber && iterable.to is PtNumber)
code += translateForInConstantRange(forLoop, loopvar)
else
code += translateForInNonConstantRange(forLoop, loopvar)
}
is PtIdentifier -> {
val arrayAddress = allocations.get(iterable.targetName)
val iterableVar = symbolTable.lookup(iterable.targetName) as StStaticVariable
val loopvarAddress = allocations.get(loopvar.scopedName)
val indexReg = vmRegisters.nextFree()
val tmpReg = vmRegisters.nextFree()
val loopLabel = createLabelName()
val endLabel = createLabelName()
if(iterableVar.dt==DataType.STR) {
// iterate over a zero-terminated string
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=indexReg, value=0)
code += VmCodeLabel(loopLabel)
code += VmCodeInstruction(Opcode.LOADX, VmDataType.BYTE, reg1=tmpReg, reg2=indexReg, value = arrayAddress)
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=tmpReg, labelSymbol = endLabel)
code += VmCodeInstruction(Opcode.STOREM, VmDataType.BYTE, reg1=tmpReg, value = loopvarAddress)
code += translateNode(forLoop.statements)
code += VmCodeInstruction(Opcode.INC, VmDataType.BYTE, reg1=indexReg)
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = loopLabel)
code += VmCodeLabel(endLabel)
} else {
// iterate over array
val elementDt = ArrayToElementTypes.getValue(iterable.type)
val elementSize = program.memsizer.memorySize(elementDt)
val lengthBytes = iterableVar.length!! * elementSize
if(lengthBytes<256) {
val lengthReg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=indexReg, value=0)
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=lengthReg, value=lengthBytes)
code += VmCodeLabel(loopLabel)
code += VmCodeInstruction(Opcode.LOADX, vmType(elementDt), reg1=tmpReg, reg2=indexReg, value=arrayAddress)
code += VmCodeInstruction(Opcode.STOREM, vmType(elementDt), reg1=tmpReg, value = loopvarAddress)
code += translateNode(forLoop.statements)
code += addConstReg(VmDataType.BYTE, indexReg, elementSize)
code += VmCodeInstruction(Opcode.BNE, VmDataType.BYTE, reg1=indexReg, reg2=lengthReg, labelSymbol = loopLabel)
} else if(lengthBytes==256) {
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=indexReg, value=0)
code += VmCodeLabel(loopLabel)
code += VmCodeInstruction(Opcode.LOADX, vmType(elementDt), reg1=tmpReg, reg2=indexReg, value=arrayAddress)
code += VmCodeInstruction(Opcode.STOREM, vmType(elementDt), reg1=tmpReg, value = loopvarAddress)
code += translateNode(forLoop.statements)
code += addConstReg(VmDataType.BYTE, indexReg, elementSize)
code += VmCodeInstruction(Opcode.BNZ, VmDataType.BYTE, reg1=indexReg, labelSymbol = loopLabel)
} else {
throw AssemblyError("iterator length should never exceed 256")
}
}
}
else -> throw AssemblyError("weird for iterable")
}
return code
}
private fun translateForInNonConstantRange(forLoop: PtForLoop, loopvar: StStaticVariable): VmCodeChunk {
val iterable = forLoop.iterable as PtRange
val step = iterable.step.number.toInt()
if (step==0)
throw AssemblyError("step 0")
val indexReg = vmRegisters.nextFree()
val endvalueReg = vmRegisters.nextFree()
val loopvarAddress = allocations.get(loopvar.scopedName)
val loopvarDt = vmType(loopvar.dt)
val loopLabel = createLabelName()
val code = VmCodeChunk()
code += expressionEval.translateExpression(iterable.to, endvalueReg, -1)
code += expressionEval.translateExpression(iterable.from, indexReg, -1)
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1=indexReg, value=loopvarAddress)
code += VmCodeLabel(loopLabel)
code += translateNode(forLoop.statements)
if(step<3) {
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
} else {
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
code += addConstReg(loopvarDt, indexReg, step)
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
}
val branchOpcode = if(loopvar.dt in SignedDatatypes) Opcode.BLES else Opcode.BLE
code += VmCodeInstruction(branchOpcode, loopvarDt, reg1=indexReg, reg2=endvalueReg, labelSymbol=loopLabel)
return code
}
private fun translateForInConstantRange(forLoop: PtForLoop, loopvar: StStaticVariable): VmCodeChunk {
val loopLabel = createLabelName()
val loopvarAddress = allocations.get(loopvar.scopedName)
val indexReg = vmRegisters.nextFree()
val loopvarDt = vmType(loopvar.dt)
val iterable = forLoop.iterable as PtRange
val step = iterable.step.number.toInt()
val rangeStart = (iterable.from as PtNumber).number.toInt()
val rangeEndUntyped = (iterable.to as PtNumber).number.toInt() + step
if(step==0)
throw AssemblyError("step 0")
if(step>0 && rangeEndUntyped<rangeStart || step<0 && rangeEndUntyped>rangeStart)
throw AssemblyError("empty range")
val rangeEndWrapped = if(loopvarDt==VmDataType.BYTE) rangeEndUntyped and 255 else rangeEndUntyped and 65535
val code = VmCodeChunk()
val endvalueReg: Int
if(rangeEndWrapped!=0) {
endvalueReg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, loopvarDt, reg1 = endvalueReg, value = rangeEndWrapped)
} else {
endvalueReg = -1 // not used
}
code += VmCodeInstruction(Opcode.LOAD, loopvarDt, reg1=indexReg, value=rangeStart)
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1=indexReg, value=loopvarAddress)
code += VmCodeLabel(loopLabel)
code += translateNode(forLoop.statements)
if(step<3) {
code += addConstMem(loopvarDt, loopvarAddress.toUInt(), step)
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
} else {
code += VmCodeInstruction(Opcode.LOADM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
code += addConstReg(loopvarDt, indexReg, step)
code += VmCodeInstruction(Opcode.STOREM, loopvarDt, reg1 = indexReg, value = loopvarAddress)
}
code += if(rangeEndWrapped==0) {
VmCodeInstruction(Opcode.BNZ, loopvarDt, reg1 = indexReg, labelSymbol = loopLabel)
} else {
VmCodeInstruction(Opcode.BNE, loopvarDt, reg1 = indexReg, reg2 = endvalueReg, labelSymbol = loopLabel)
}
return code
}
private fun addConstReg(dt: VmDataType, reg: Int, value: Int): VmCodeChunk {
val code = VmCodeChunk()
when(value) {
0 -> { /* do nothing */ }
1 -> {
code += VmCodeInstruction(Opcode.INC, dt, reg1=reg)
}
2 -> {
code += VmCodeInstruction(Opcode.INC, dt, reg1=reg)
code += VmCodeInstruction(Opcode.INC, dt, reg1=reg)
}
-1 -> {
code += VmCodeInstruction(Opcode.DEC, dt, reg1=reg)
}
-2 -> {
code += VmCodeInstruction(Opcode.DEC, dt, reg1=reg)
code += VmCodeInstruction(Opcode.DEC, dt, reg1=reg)
}
else -> {
val valueReg = vmRegisters.nextFree()
if(value>0) {
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=valueReg, value= value)
code += VmCodeInstruction(Opcode.ADD, dt, reg1 = reg, reg2 = valueReg)
}
else {
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=valueReg, value= -value)
code += VmCodeInstruction(Opcode.SUB, dt, reg1 = reg, reg2 = valueReg)
}
}
}
return code
}
private fun addConstMem(dt: VmDataType, address: UInt, value: Int): VmCodeChunk {
val code = VmCodeChunk()
when(value) {
0 -> { /* do nothing */ }
1 -> {
code += VmCodeInstruction(Opcode.INCM, dt, value=address.toInt())
}
2 -> {
code += VmCodeInstruction(Opcode.INCM, dt, value=address.toInt())
code += VmCodeInstruction(Opcode.INCM, dt, value=address.toInt())
}
-1 -> {
code += VmCodeInstruction(Opcode.DECM, dt, value=address.toInt())
}
-2 -> {
code += VmCodeInstruction(Opcode.DECM, dt, value=address.toInt())
code += VmCodeInstruction(Opcode.DECM, dt, value=address.toInt())
}
else -> {
val valueReg = vmRegisters.nextFree()
val operandReg = vmRegisters.nextFree()
if(value>0) {
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=value)
code += VmCodeInstruction(Opcode.ADD, dt, reg1 = valueReg, reg2 = operandReg)
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
}
else {
code += VmCodeInstruction(Opcode.LOADM, dt, reg1=valueReg, value=address.toInt())
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=operandReg, value=-value)
code += VmCodeInstruction(Opcode.SUB, dt, reg1 = valueReg, reg2 = operandReg)
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=valueReg, value=address.toInt())
}
}
}
return code
}
internal fun multiplyByConstFloat(fpReg: Int, factor: Float): VmCodeChunk {
val code = VmCodeChunk()
if(factor==1f)
return code
if(factor==0f) {
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = fpReg, fpValue = 0f)
} else {
val factorReg = vmRegisters.nextFreeFloat()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
code += VmCodeInstruction(Opcode.MUL, VmDataType.FLOAT, fpReg1 = fpReg, fpReg2 = factorReg)
}
return code
}
internal fun multiplyByConstFloatInplace(address: Int, factor: Float): VmCodeChunk {
val code = VmCodeChunk()
if(factor==1f)
return code
if(factor==0f) {
code += VmCodeInstruction(Opcode.STOREZM, VmDataType.FLOAT, value = address)
} else {
val factorReg = vmRegisters.nextFreeFloat()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
code += VmCodeInstruction(Opcode.MULM, VmDataType.FLOAT, fpReg1 = factorReg, value = address)
}
return code
}
internal val powersOfTwo = (0..16).map { 2.0.pow(it.toDouble()).toInt() }
internal fun multiplyByConst(dt: VmDataType, reg: Int, factor: Int): VmCodeChunk {
val code = VmCodeChunk()
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += VmCodeInstruction(Opcode.LSL, dt, reg1=reg)
}
else if(pow2>=1) {
// just shift multiple bits
val pow2reg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
code += VmCodeInstruction(Opcode.LSLN, dt, reg1=reg, reg2=pow2reg)
} else {
if (factor == 0) {
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0)
}
else {
val factorReg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value= factor)
code += VmCodeInstruction(Opcode.MUL, dt, reg1=reg, reg2=factorReg)
}
}
return code
}
internal fun multiplyByConstInplace(dt: VmDataType, address: Int, factor: Int): VmCodeChunk {
val code = VmCodeChunk()
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += VmCodeInstruction(Opcode.LSLM, dt, value = address)
}
else if(pow2>=1) {
// just shift multiple bits
val pow2reg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
code += VmCodeInstruction(Opcode.LSLNM, dt, reg1=pow2reg, value=address)
} else {
if (factor == 0) {
code += VmCodeInstruction(Opcode.STOREZM, dt, value=address)
}
else {
val factorReg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value = factor)
code += VmCodeInstruction(Opcode.MULM, dt, reg1=factorReg, value = address)
}
}
return code
}
internal fun divideByConstFloat(fpReg: Int, factor: Float): VmCodeChunk {
val code = VmCodeChunk()
if(factor==1f)
return code
if(factor==0f) {
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = fpReg, fpValue = Float.MAX_VALUE)
} else {
val factorReg = vmRegisters.nextFreeFloat()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
code += VmCodeInstruction(Opcode.DIVS, VmDataType.FLOAT, fpReg1 = fpReg, fpReg2 = factorReg)
}
return code
}
internal fun divideByConstFloatInplace(address: Int, factor: Float): VmCodeChunk {
val code = VmCodeChunk()
if(factor==1f)
return code
if(factor==0f) {
val maxvalueReg = vmRegisters.nextFreeFloat()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1 = maxvalueReg, fpValue = Float.MAX_VALUE)
code += VmCodeInstruction(Opcode.STOREM, VmDataType.FLOAT, fpReg1 = maxvalueReg, value=address)
} else {
val factorReg = vmRegisters.nextFreeFloat()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.FLOAT, fpReg1=factorReg, fpValue = factor)
code += VmCodeInstruction(Opcode.DIVSM, VmDataType.FLOAT, fpReg1 = factorReg, value=address)
}
return code
}
internal fun divideByConst(dt: VmDataType, reg: Int, factor: Int, signed: Boolean): VmCodeChunk {
val code = VmCodeChunk()
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += if(signed)
VmCodeInstruction(Opcode.ASR, dt, reg1=reg)
else
VmCodeInstruction(Opcode.LSR, dt, reg1=reg)
}
else if(pow2>=1) {
// just shift multiple bits
val pow2reg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
code += if(signed)
VmCodeInstruction(Opcode.ASRN, dt, reg1=reg, reg2=pow2reg)
else
VmCodeInstruction(Opcode.LSRN, dt, reg1=reg, reg2=pow2reg)
} else {
if (factor == 0) {
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0xffff)
}
else {
val factorReg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value= factor)
code += if(signed)
VmCodeInstruction(Opcode.DIVS, dt, reg1=reg, reg2=factorReg)
else
VmCodeInstruction(Opcode.DIV, dt, reg1=reg, reg2=factorReg)
}
}
return code
}
internal fun divideByConstInplace(dt: VmDataType, address: Int, factor: Int, signed: Boolean): VmCodeChunk {
val code = VmCodeChunk()
if(factor==1)
return code
val pow2 = powersOfTwo.indexOf(factor)
if(pow2==1) {
// just shift 1 bit
code += if(signed)
VmCodeInstruction(Opcode.ASRM, dt, value=address)
else
VmCodeInstruction(Opcode.LSRM, dt, value=address)
}
else if(pow2>=1) {
// just shift multiple bits
val pow2reg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=pow2reg, value=pow2)
code += if(signed)
VmCodeInstruction(Opcode.ASRNM, dt, reg1=pow2reg, value=address)
else
VmCodeInstruction(Opcode.LSRNM, dt, reg1=pow2reg, value=address)
} else {
if (factor == 0) {
val reg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=reg, value=0xffff)
code += VmCodeInstruction(Opcode.STOREM, dt, reg1=reg, value=address)
}
else {
val factorReg = vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, dt, reg1=factorReg, value= factor)
code += if(signed)
VmCodeInstruction(Opcode.DIVSM, dt, reg1=factorReg, value=address)
else
VmCodeInstruction(Opcode.DIVM, dt, reg1=factorReg, value=address)
}
}
return code
}
private fun translate(ifElse: PtIfElse): VmCodeChunk {
if(ifElse.condition.operator !in ComparisonOperators)
throw AssemblyError("if condition should only be a binary comparison expression")
val signed = ifElse.condition.left.type in arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
val vmDt = vmType(ifElse.condition.left.type)
val code = VmCodeChunk()
fun translateNonZeroComparison(): VmCodeChunk {
val elseBranch = when(ifElse.condition.operator) {
"==" -> Opcode.BNE
"!=" -> Opcode.BEQ
"<" -> if(signed) Opcode.BGES else Opcode.BGE
">" -> if(signed) Opcode.BLES else Opcode.BLE
"<=" -> if(signed) Opcode.BGTS else Opcode.BGT
">=" -> if(signed) Opcode.BLTS else Opcode.BLT
else -> throw AssemblyError("invalid comparison operator")
}
val leftReg = vmRegisters.nextFree()
val rightReg = vmRegisters.nextFree()
code += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
code += expressionEval.translateExpression(ifElse.condition.right, rightReg, -1)
if(ifElse.elseScope.children.isNotEmpty()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, reg2=rightReg, labelSymbol = elseLabel)
code += translateNode(ifElse.ifScope)
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = afterIfLabel)
code += VmCodeLabel(elseLabel)
code += translateNode(ifElse.elseScope)
code += VmCodeLabel(afterIfLabel)
} else {
// only if part
val afterIfLabel = createLabelName()
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, reg2=rightReg, labelSymbol = afterIfLabel)
code += translateNode(ifElse.ifScope)
code += VmCodeLabel(afterIfLabel)
}
return code
}
fun translateZeroComparison(): VmCodeChunk {
fun equalOrNotEqualZero(elseBranch: Opcode): VmCodeChunk {
val leftReg = vmRegisters.nextFree()
code += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
if(ifElse.elseScope.children.isNotEmpty()) {
// if and else parts
val elseLabel = createLabelName()
val afterIfLabel = createLabelName()
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, labelSymbol = elseLabel)
code += translateNode(ifElse.ifScope)
code += VmCodeInstruction(Opcode.JUMP, labelSymbol = afterIfLabel)
code += VmCodeLabel(elseLabel)
code += translateNode(ifElse.elseScope)
code += VmCodeLabel(afterIfLabel)
} else {
// only if part
val afterIfLabel = createLabelName()
code += VmCodeInstruction(elseBranch, vmDt, reg1=leftReg, labelSymbol = afterIfLabel)
code += translateNode(ifElse.ifScope)
code += VmCodeLabel(afterIfLabel)
}
return code
}
return when (ifElse.condition.operator) {
"==" -> {
// if X==0 ... so we just branch on left expr is Not-zero.
equalOrNotEqualZero(Opcode.BNZ)
}
"!=" -> {
// if X!=0 ... so we just branch on left expr is Zero.
equalOrNotEqualZero(Opcode.BZ)
}
else -> {
// another comparison against 0, just use regular codegen for this.
translateNonZeroComparison()
}
}
}
return if(constValue(ifElse.condition.right)==0.0)
translateZeroComparison()
else
translateNonZeroComparison()
}
private fun translate(postIncrDecr: PtPostIncrDecr): VmCodeChunk {
val code = VmCodeChunk()
val operationMem: Opcode
val operationRegister: Opcode
when(postIncrDecr.operator) {
"++" -> {
operationMem = Opcode.INCM
operationRegister = Opcode.INC
}
"--" -> {
operationMem = Opcode.DECM
operationRegister = Opcode.DEC
}
else -> throw AssemblyError("weird operator")
}
val ident = postIncrDecr.target.identifier
val memory = postIncrDecr.target.memory
val array = postIncrDecr.target.array
val vmDt = vmType(postIncrDecr.target.type)
if(ident!=null) {
val address = allocations.get(ident.targetName)
code += VmCodeInstruction(operationMem, vmDt, value = address)
} else if(memory!=null) {
if(memory.address is PtNumber) {
val address = (memory.address as PtNumber).number.toInt()
code += VmCodeInstruction(operationMem, vmDt, value = address)
} else {
val incReg = vmRegisters.nextFree()
val addressReg = vmRegisters.nextFree()
code += expressionEval.translateExpression(memory.address, addressReg, -1)
code += VmCodeInstruction(Opcode.LOADI, vmDt, reg1 = incReg, reg2 = addressReg)
code += VmCodeInstruction(operationRegister, vmDt, reg1 = incReg)
code += VmCodeInstruction(Opcode.STOREI, vmDt, reg1 = incReg, reg2 = addressReg)
}
} else if (array!=null) {
val variable = array.variable.targetName
var variableAddr = allocations.get(variable)
val itemsize = program.memsizer.memorySize(array.type)
val fixedIndex = constIntValue(array.index)
if(fixedIndex!=null) {
variableAddr += fixedIndex*itemsize
code += VmCodeInstruction(operationMem, vmDt, value=variableAddr)
} else {
val incReg = vmRegisters.nextFree()
val indexReg = vmRegisters.nextFree()
code += expressionEval.translateExpression(array.index, indexReg, -1)
code += VmCodeInstruction(Opcode.LOADX, vmDt, reg1=incReg, reg2=indexReg, value=variableAddr)
code += VmCodeInstruction(operationRegister, vmDt, reg1=incReg)
code += VmCodeInstruction(Opcode.STOREX, vmDt, reg1=incReg, reg2=indexReg, value=variableAddr)
}
} else
throw AssemblyError("weird assigntarget")
return code
}
private fun translate(repeat: PtRepeatLoop): VmCodeChunk {
when (constIntValue(repeat.count)) {
0 -> return VmCodeChunk()
1 -> return translateGroup(repeat.children)
256 -> {
// 256 iterations can still be done with just a byte counter if you set it to zero as starting value.
repeat.children[0] = PtNumber(DataType.UBYTE, 0.0, repeat.count.position)
}
}
val code = VmCodeChunk()
val counterReg = vmRegisters.nextFree()
val vmDt = vmType(repeat.count.type)
code += expressionEval.translateExpression(repeat.count, counterReg, -1)
val repeatLabel = createLabelName()
code += VmCodeLabel(repeatLabel)
code += translateNode(repeat.statements)
code += VmCodeInstruction(Opcode.DEC, vmDt, reg1=counterReg)
code += VmCodeInstruction(Opcode.BNZ, vmDt, reg1=counterReg, labelSymbol = repeatLabel)
return code
}
private fun translate(jump: PtJump): VmCodeChunk {
val code = VmCodeChunk()
if(jump.address!=null)
throw AssemblyError("cannot jump to memory location in the vm target")
code += if(jump.generatedLabel!=null)
VmCodeInstruction(Opcode.JUMP, labelSymbol = listOf(jump.generatedLabel!!))
else if(jump.identifier!=null)
VmCodeInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.targetName)
else
throw AssemblyError("weird jump")
return code
}
private fun translateGroup(group: List<PtNode>): VmCodeChunk {
val code = VmCodeChunk()
group.forEach { code += translateNode(it) }
return code
}
private fun translate(ret: PtReturn): VmCodeChunk {
val code = VmCodeChunk()
val value = ret.value
if(value!=null) {
// Call Convention: return value is always returned in r0 (or fr0 if float)
code += if(value.type==DataType.FLOAT)
expressionEval.translateExpression(value, -1, 0)
else
expressionEval.translateExpression(value, 0, -1)
}
code += VmCodeInstruction(Opcode.RETURN)
return code
}
private fun translate(sub: PtSub): VmCodeChunk {
val code = VmCodeChunk()
code += VmCodeComment("SUB: ${sub.scopedName} -> ${sub.returntype}")
code += VmCodeLabel(sub.scopedName)
for (child in sub.children) {
code += translateNode(child)
}
code += VmCodeComment("SUB-END '${sub.name}'")
return code
}
private fun translate(block: PtBlock): VmCodeChunk {
val code = VmCodeChunk()
code += VmCodeComment("BLOCK '${block.name}' addr=${block.address} lib=${block.library}")
for (child in block.children) {
if(child !is PtAssignment) // global variable initialization is done elsewhere
code += translateNode(child)
}
code += VmCodeComment("BLOCK-END '${block.name}'")
return code
}
internal fun vmType(type: DataType): VmDataType {
return when(type) {
DataType.UBYTE,
DataType.BYTE -> VmDataType.BYTE
DataType.UWORD,
DataType.WORD -> VmDataType.WORD
DataType.FLOAT -> VmDataType.FLOAT
in PassByReferenceDatatypes -> VmDataType.WORD
else -> throw AssemblyError("no vm datatype for $type")
}
}
private var labelSequenceNumber = 0
internal fun createLabelName(): List<String> {
labelSequenceNumber++
return listOf("prog8_label_gen_$labelSequenceNumber")
}
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk =
builtinFuncGen.translate(call, resultRegister)
internal fun isZero(expression: PtExpression): Boolean = expression is PtNumber && expression.number==0.0
internal fun isOne(expression: PtExpression): Boolean = expression is PtNumber && expression.number==1.0
}

View File

@ -0,0 +1,846 @@
package prog8.codegen.virtual
import prog8.code.StStaticVariable
import prog8.code.StSub
import prog8.code.ast.*
import prog8.code.core.*
import prog8.vm.Opcode
import prog8.vm.VmDataType
internal class ExpressionGen(private val codeGen: CodeGen) {
fun translateExpression(expr: PtExpression, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
require(codeGen.vmRegisters.peekNext() > resultRegister)
val code = VmCodeChunk()
when (expr) {
is PtMachineRegister -> {
if(resultRegister!=expr.register) {
val vmDt = codeGen.vmType(expr.type)
code += VmCodeInstruction(Opcode.LOADR, vmDt, reg1=resultRegister, reg2=expr.register)
}
}
is PtNumber -> {
val vmDt = codeGen.vmType(expr.type)
code += if(vmDt==VmDataType.FLOAT)
VmCodeInstruction(Opcode.LOAD, vmDt, fpReg1 = resultFpRegister, fpValue = expr.number.toFloat())
else
VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=expr.number.toInt())
}
is PtIdentifier -> {
val vmDt = codeGen.vmType(expr.type)
val mem = codeGen.allocations.get(expr.targetName)
code += if (expr.type in PassByValueDatatypes) {
if(vmDt==VmDataType.FLOAT)
VmCodeInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, value = mem)
else
VmCodeInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, value = mem)
} else {
// for strings and arrays etc., load the *address* of the value instead
VmCodeInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, value = mem)
}
}
is PtAddressOf -> {
val vmDt = codeGen.vmType(expr.type)
val mem = codeGen.allocations.get(expr.identifier.targetName)
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=mem)
}
is PtMemoryByte -> {
if(expr.address is PtNumber) {
val address = (expr.address as PtNumber).number.toInt()
code += VmCodeInstruction(Opcode.LOADM, VmDataType.BYTE, reg1=resultRegister, value = address)
} else {
val addressRegister = codeGen.vmRegisters.nextFree()
code += translateExpression(expr.address, addressRegister, -1)
code += VmCodeInstruction(Opcode.LOADI, VmDataType.BYTE, reg1=resultRegister, reg2=addressRegister)
}
}
is PtTypeCast -> code += translate(expr, resultRegister, resultFpRegister)
is PtPrefix -> code += translate(expr, resultRegister)
is PtArrayIndexer -> code += translate(expr, resultRegister, resultFpRegister)
is PtBinaryExpression -> code += translate(expr, resultRegister, resultFpRegister)
is PtBuiltinFunctionCall -> code += codeGen.translateBuiltinFunc(expr, resultRegister)
is PtFunctionCall -> code += translate(expr, resultRegister, resultFpRegister)
is PtContainmentCheck -> code += translate(expr, resultRegister, resultFpRegister)
is PtPipe -> code += translate(expr, resultRegister)
is PtRange,
is PtArray,
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
else -> throw AssemblyError("weird expression")
}
return code
}
internal fun translate(pipe: PtPipe, resultRegister: Int): VmCodeChunk {
val segments = pipe.segments
var valueDt = segments[0].type
var valueReg = if(pipe.void) codeGen.vmRegisters.nextFree() else resultRegister
fun addImplicitArgToSegment(segment: PtExpression, sourceReg: Int, sourceDt: DataType): PtExpression {
return when (segment) {
is PtFunctionCall -> {
val segWithArg = PtFunctionCall(segment.functionName, segment.void, segment.type, segment.position)
segWithArg.children.add(PtMachineRegister(sourceReg, sourceDt, segment.position))
segWithArg.children.addAll(segment.args)
segWithArg
}
is PtBuiltinFunctionCall -> {
val segWithArg = PtBuiltinFunctionCall(segment.name, segment.void, segment.hasNoSideEffects, segment.type, segment.position)
segWithArg.children.add(PtMachineRegister(sourceReg, sourceDt, segment.position))
segWithArg.children.addAll(segment.args)
segWithArg
}
else -> throw AssemblyError("weird segment type")
}
}
val code = VmCodeChunk()
code += translateExpression(segments[0], valueReg, -1)
for (segment in segments.subList(1, segments.size-1)) {
val sourceReg = valueReg
val sourceDt = valueDt
if(segment.type!=valueDt) {
valueDt = segment.type
valueReg = codeGen.vmRegisters.nextFree()
}
val segmentWithImplicitArgument = addImplicitArgToSegment(segment, sourceReg, sourceDt)
code += translateExpression(segmentWithImplicitArgument, valueReg, -1)
}
val segWithArg = addImplicitArgToSegment(segments.last(), valueReg, valueDt)
code += translateExpression(segWithArg, resultRegister, -1)
return code
}
private fun translate(check: PtContainmentCheck, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
code += translateExpression(check.element, resultRegister, -1) // load the element to check in resultRegister
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.targetName) as StStaticVariable
when(iterable.dt) {
DataType.STR -> {
val call = PtFunctionCall(listOf("prog8_lib", "string_contains"), false, DataType.UBYTE, check.position)
call.children.add(check.element)
call.children.add(check.iterable)
code += translate(call, resultRegister, resultFpRegister)
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
val call = PtFunctionCall(listOf("prog8_lib", "bytearray_contains"), false, DataType.UBYTE, check.position)
call.children.add(check.element)
call.children.add(check.iterable)
call.children.add(PtNumber(DataType.UBYTE, iterable.length!!.toDouble(), iterable.position))
code += translate(call, resultRegister, resultFpRegister)
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
val call = PtFunctionCall(listOf("prog8_lib", "wordarray_contains"), false, DataType.UBYTE, check.position)
call.children.add(check.element)
call.children.add(check.iterable)
call.children.add(PtNumber(DataType.UBYTE, iterable.length!!.toDouble(), iterable.position))
code += translate(call, resultRegister, resultFpRegister)
}
DataType.ARRAY_F -> throw AssemblyError("containment check in float-array not supported")
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.targetName}")
}
return code
}
private fun translate(arrayIx: PtArrayIndexer, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
val eltSize = codeGen.program.memsizer.memorySize(arrayIx.type)
val vmDt = codeGen.vmType(arrayIx.type)
val code = VmCodeChunk()
val idxReg = codeGen.vmRegisters.nextFree()
val arrayLocation = codeGen.allocations.get(arrayIx.variable.targetName)
if(arrayIx.index is PtNumber) {
// optimized code when index is known - just calculate the memory address here
val memOffset = (arrayIx.index as PtNumber).number.toInt() * eltSize
if(vmDt==VmDataType.FLOAT)
code += VmCodeInstruction(Opcode.LOADM, VmDataType.FLOAT, fpReg1=resultFpRegister, value=arrayLocation+memOffset)
else
code += VmCodeInstruction(Opcode.LOADM, vmDt, reg1=resultRegister, value=arrayLocation+memOffset)
} else {
code += translateExpression(arrayIx.index, idxReg, -1)
if(eltSize>1)
code += codeGen.multiplyByConst(VmDataType.BYTE, idxReg, eltSize)
if(vmDt==VmDataType.FLOAT)
code += VmCodeInstruction(Opcode.LOADX, VmDataType.FLOAT, fpReg1 = resultFpRegister, reg1=idxReg, value = arrayLocation)
else
code += VmCodeInstruction(Opcode.LOADX, vmDt, reg1=resultRegister, reg2=idxReg, value = arrayLocation)
}
return code
}
private fun translate(expr: PtPrefix, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
code += translateExpression(expr.value, resultRegister, -1)
val vmDt = codeGen.vmType(expr.type)
when(expr.operator) {
"+" -> { }
"-" -> {
code += VmCodeInstruction(Opcode.NEG, vmDt, reg1=resultRegister)
}
"~" -> {
val regMask = codeGen.vmRegisters.nextFree()
val mask = if(vmDt==VmDataType.BYTE) 0x00ff else 0xffff
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value=mask)
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=regMask)
}
"not" -> {
code += VmCodeInstruction(Opcode.NOT, vmDt, reg1=resultRegister)
}
else -> throw AssemblyError("weird prefix operator")
}
return code
}
private fun translate(cast: PtTypeCast, predefinedResultRegister: Int, predefinedResultFpRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
if(cast.type==cast.value.type)
return code
val actualResultFpReg = if(predefinedResultFpRegister>=0) predefinedResultFpRegister else codeGen.vmRegisters.nextFreeFloat()
val actualResultReg = if(predefinedResultRegister>=0) predefinedResultRegister else codeGen.vmRegisters.nextFree()
if(cast.value.type==DataType.FLOAT) {
// a cast from float to integer, so evaluate the value into a float register first
code += translateExpression(cast.value, -1, actualResultFpReg)
}
else
code += translateExpression(cast.value, actualResultReg, -1)
when(cast.type) {
DataType.UBYTE -> {
when(cast.value.type) {
DataType.BYTE, DataType.UWORD, DataType.WORD -> { /* just keep the LSB as it is */ }
DataType.FLOAT -> code += VmCodeInstruction(Opcode.FTOUB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
else -> throw AssemblyError("weird cast value type")
}
}
DataType.BYTE -> {
when(cast.value.type) {
DataType.UBYTE, DataType.UWORD, DataType.WORD -> { /* just keep the LSB as it is */ }
DataType.FLOAT -> code += VmCodeInstruction(Opcode.FTOSB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
else -> throw AssemblyError("weird cast value type")
}
}
DataType.UWORD -> {
when(cast.value.type) {
DataType.BYTE -> {
// byte -> uword: sign extend
code += VmCodeInstruction(Opcode.EXTS, type = VmDataType.BYTE, reg1 = actualResultReg)
}
DataType.UBYTE -> {
// ubyte -> uword: sign extend
code += VmCodeInstruction(Opcode.EXT, type = VmDataType.BYTE, reg1 = actualResultReg)
}
DataType.WORD -> { }
DataType.FLOAT -> {
code += VmCodeInstruction(Opcode.FTOUW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
}
else -> throw AssemblyError("weird cast value type")
}
}
DataType.WORD -> {
when(cast.value.type) {
DataType.BYTE -> {
// byte -> word: sign extend
code += VmCodeInstruction(Opcode.EXTS, type = VmDataType.BYTE, reg1 = actualResultReg)
}
DataType.UBYTE -> {
// byte -> word: sign extend
code += VmCodeInstruction(Opcode.EXT, type = VmDataType.BYTE, reg1 = actualResultReg)
}
DataType.UWORD -> { }
DataType.FLOAT -> {
code += VmCodeInstruction(Opcode.FTOSW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
}
else -> throw AssemblyError("weird cast value type")
}
}
DataType.FLOAT -> {
when(cast.value.type) {
DataType.UBYTE -> {
code += VmCodeInstruction(Opcode.FFROMUB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
}
DataType.BYTE -> {
code += VmCodeInstruction(Opcode.FFROMSB, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
}
DataType.UWORD -> {
code += VmCodeInstruction(Opcode.FFROMUW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
}
DataType.WORD -> {
code += VmCodeInstruction(Opcode.FFROMSW, VmDataType.FLOAT, reg1=actualResultReg, fpReg1 = actualResultFpReg)
}
else -> throw AssemblyError("weird cast value type")
}
}
else -> throw AssemblyError("weird cast type")
}
return code
}
private fun translate(binExpr: PtBinaryExpression, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
val vmDt = codeGen.vmType(binExpr.left.type)
val signed = binExpr.left.type in SignedDatatypes
return when(binExpr.operator) {
"+" -> operatorPlus(binExpr, vmDt, resultRegister, resultFpRegister)
"-" -> operatorMinus(binExpr, vmDt, resultRegister, resultFpRegister)
"*" -> operatorMultiply(binExpr, vmDt, resultRegister, resultFpRegister)
"/" -> operatorDivide(binExpr, vmDt, resultRegister, resultFpRegister, signed)
"%" -> operatorModulo(binExpr, vmDt, resultRegister)
"|", "or" -> operatorOr(binExpr, vmDt, resultRegister)
"&", "and" -> operatorAnd(binExpr, vmDt, resultRegister)
"^", "xor" -> operatorXor(binExpr, vmDt, resultRegister)
"<<" -> operatorShiftLeft(binExpr, vmDt, resultRegister)
">>" -> operatorShiftRight(binExpr, vmDt, resultRegister, signed)
"==" -> operatorEquals(binExpr, vmDt, resultRegister, false)
"!=" -> operatorEquals(binExpr, vmDt, resultRegister, true)
"<" -> operatorLessThan(binExpr, vmDt, resultRegister, signed, false)
">" -> operatorGreaterThan(binExpr, vmDt, resultRegister, signed, false)
"<=" -> operatorLessThan(binExpr, vmDt, resultRegister, signed, true)
">=" -> operatorGreaterThan(binExpr, vmDt, resultRegister, signed, true)
else -> throw AssemblyError("weird operator ${binExpr.operator}")
}
}
private fun operatorGreaterThan(
binExpr: PtBinaryExpression,
vmDt: VmDataType,
resultRegister: Int,
signed: Boolean,
greaterEquals: Boolean
): VmCodeChunk {
val code = VmCodeChunk()
if(vmDt==VmDataType.FLOAT) {
val leftFpReg = codeGen.vmRegisters.nextFreeFloat()
val rightFpReg = codeGen.vmRegisters.nextFreeFloat()
val zeroRegister = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, -1, leftFpReg)
code += translateExpression(binExpr.right, -1, rightFpReg)
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
val ins = if (signed) {
if (greaterEquals) Opcode.SGES else Opcode.SGTS
} else {
if (greaterEquals) Opcode.SGE else Opcode.SGT
}
code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister)
} else {
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
comparisonCall.children.add(binExpr.left)
comparisonCall.children.add(binExpr.right)
code += translate(comparisonCall, resultRegister, -1)
val zeroRegister = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
code += if(greaterEquals)
VmCodeInstruction(Opcode.SGES, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
else
VmCodeInstruction(Opcode.SGTS, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
} else {
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
val ins = if (signed) {
if (greaterEquals) Opcode.SGES else Opcode.SGTS
} else {
if (greaterEquals) Opcode.SGE else Opcode.SGT
}
code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
}
}
return code
}
private fun operatorLessThan(
binExpr: PtBinaryExpression,
vmDt: VmDataType,
resultRegister: Int,
signed: Boolean,
lessEquals: Boolean
): VmCodeChunk {
val code = VmCodeChunk()
if(vmDt==VmDataType.FLOAT) {
val leftFpReg = codeGen.vmRegisters.nextFreeFloat()
val rightFpReg = codeGen.vmRegisters.nextFreeFloat()
val zeroRegister = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, -1, leftFpReg)
code += translateExpression(binExpr.right, -1, rightFpReg)
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
val ins = if (signed) {
if (lessEquals) Opcode.SLES else Opcode.SLTS
} else {
if (lessEquals) Opcode.SLE else Opcode.SLT
}
code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister)
} else {
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
comparisonCall.children.add(binExpr.left)
comparisonCall.children.add(binExpr.right)
code += translate(comparisonCall, resultRegister, -1)
val zeroRegister = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0)
code += if(lessEquals)
VmCodeInstruction(Opcode.SLES, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
else
VmCodeInstruction(Opcode.SLTS, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister)
} else {
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
val ins = if (signed) {
if (lessEquals) Opcode.SLES else Opcode.SLTS
} else {
if (lessEquals) Opcode.SLE else Opcode.SLT
}
code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
}
}
return code
}
private fun operatorEquals(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, notEquals: Boolean): VmCodeChunk {
val code = VmCodeChunk()
if(vmDt==VmDataType.FLOAT) {
val leftFpReg = codeGen.vmRegisters.nextFreeFloat()
val rightFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(binExpr.left, -1, leftFpReg)
code += translateExpression(binExpr.right, -1, rightFpReg)
if (notEquals) {
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
} else {
val label = codeGen.createLabelName()
val valueReg = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=1)
code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=valueReg, fpReg1 = leftFpReg, fpReg2 = rightFpReg)
code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=valueReg, labelSymbol = label)
code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=0)
code += VmCodeLabel(label)
}
} else {
if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) {
val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY)
comparisonCall.children.add(binExpr.left)
comparisonCall.children.add(binExpr.right)
code += translate(comparisonCall, resultRegister, -1)
if(notEquals) {
val maskReg = codeGen.vmRegisters.nextFree()
code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=maskReg, value=1)
code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, reg2=maskReg)
} else {
code += VmCodeInstruction(Opcode.NOT, vmDt, reg1=resultRegister)
}
} else {
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ
code += VmCodeInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
}
}
return code
}
private fun operatorShiftRight(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, signed: Boolean): VmCodeChunk {
val code = VmCodeChunk()
if(codeGen.isOne(binExpr.right)) {
code += translateExpression(binExpr.left, resultRegister, -1)
val opc = if (signed) Opcode.ASR else Opcode.LSR
code += VmCodeInstruction(opc, vmDt, reg1 = resultRegister)
} else {
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
val opc = if (signed) Opcode.ASRN else Opcode.LSRN
code += VmCodeInstruction(opc, vmDt, reg1 = resultRegister, reg2 = rightResultReg)
}
return code
}
internal fun operatorShiftRightInplace(address: Int, vmDt: VmDataType, signed: Boolean, operand: PtExpression): VmCodeChunk {
val code = VmCodeChunk()
if(codeGen.isOne(operand)) {
val opc = if (signed) Opcode.ASRM else Opcode.LSRM
code += VmCodeInstruction(opc, vmDt, value=address)
} else {
val operandReg = codeGen.vmRegisters.nextFree()
code += translateExpression(operand, operandReg, -1)
val opc = if (signed) Opcode.ASRNM else Opcode.LSRNM
code += VmCodeInstruction(opc, vmDt, reg1 = operandReg, value=address)
}
return code
}
private fun operatorShiftLeft(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
if(codeGen.isOne(binExpr.right)){
code += translateExpression(binExpr.left, resultRegister, -1)
code += VmCodeInstruction(Opcode.LSL, vmDt, reg1=resultRegister)
} else {
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
code += VmCodeInstruction(Opcode.LSLN, vmDt, reg1=resultRegister, rightResultReg)
}
return code
}
internal fun operatorShiftLeftInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
val code = VmCodeChunk()
if(codeGen.isOne(operand)){
code += VmCodeInstruction(Opcode.LSLM, vmDt, value=address)
} else {
val operandReg = codeGen.vmRegisters.nextFree()
code += translateExpression(operand, operandReg, -1)
code += VmCodeInstruction(Opcode.LSLNM, vmDt, reg1=operandReg, value=address)
}
return code
}
private fun operatorXor(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=rightResultReg)
return code
}
internal fun operatorXorInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
val code = VmCodeChunk()
val operandReg = codeGen.vmRegisters.nextFree()
code += translateExpression(operand, operandReg, -1)
code += VmCodeInstruction(Opcode.XORM, vmDt, reg1=operandReg, value = address)
return code
}
private fun operatorAnd(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, reg2=rightResultReg)
return code
}
internal fun operatorAndInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
val code = VmCodeChunk()
val operandReg = codeGen.vmRegisters.nextFree()
code += translateExpression(operand, operandReg, -1)
code += VmCodeInstruction(Opcode.ANDM, vmDt, reg1=operandReg, value=address)
return code
}
private fun operatorOr(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
code += VmCodeInstruction(Opcode.OR, vmDt, reg1=resultRegister, reg2=rightResultReg)
return code
}
internal fun operatorOrInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
val code = VmCodeChunk()
val operandReg = codeGen.vmRegisters.nextFree()
code += translateExpression(operand, operandReg, -1)
code += VmCodeInstruction(Opcode.ORM, vmDt, reg1=operandReg, value = address)
return code
}
private fun operatorModulo(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int): VmCodeChunk {
if(vmDt==VmDataType.FLOAT)
throw IllegalArgumentException("floating-point modulo not supported")
val code = VmCodeChunk()
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
code += VmCodeInstruction(Opcode.MOD, vmDt, reg1=resultRegister, reg2=rightResultReg)
return code
}
private fun operatorDivide(binExpr: PtBinaryExpression,
vmDt: VmDataType,
resultRegister: Int,
resultFpRegister: Int,
signed: Boolean): VmCodeChunk {
val code = VmCodeChunk()
val constFactorRight = binExpr.right as? PtNumber
if(vmDt==VmDataType.FLOAT) {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
code += translateExpression(binExpr.left, -1, resultFpRegister)
val factor = constFactorRight.number.toFloat()
code += codeGen.divideByConstFloat(resultFpRegister, factor)
} else {
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(binExpr.left, -1, resultFpRegister)
code += translateExpression(binExpr.right, -1, rightResultFpReg)
code += if(signed)
VmCodeInstruction(Opcode.DIVS, vmDt, fpReg1 = resultFpRegister, fpReg2=rightResultFpReg)
else
VmCodeInstruction(Opcode.DIV, vmDt, fpReg1 = resultFpRegister, fpReg2=rightResultFpReg)
}
} else {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
code += translateExpression(binExpr.left, resultRegister, -1)
val factor = constFactorRight.number.toInt()
code += codeGen.divideByConst(vmDt, resultRegister, factor, signed)
} else {
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
code += if(signed)
VmCodeInstruction(Opcode.DIVS, vmDt, reg1=resultRegister, reg2=rightResultReg)
else
VmCodeInstruction(Opcode.DIV, vmDt, reg1=resultRegister, reg2=rightResultReg)
}
}
return code
}
internal fun operatorDivideInplace(address: Int, vmDt: VmDataType, signed: Boolean, operand: PtExpression): VmCodeChunk {
val code = VmCodeChunk()
val constFactorRight = operand as? PtNumber
if(vmDt==VmDataType.FLOAT) {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
val factor = constFactorRight.number.toFloat()
code += codeGen.divideByConstFloatInplace(address, factor)
} else {
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(operand, -1, operandFpReg)
code += if(signed)
VmCodeInstruction(Opcode.DIVSM, vmDt, fpReg1 = operandFpReg, value=address)
else
VmCodeInstruction(Opcode.DIVM, vmDt, fpReg1 = operandFpReg, value=address)
}
} else {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
val factor = constFactorRight.number.toInt()
code += codeGen.divideByConstInplace(vmDt, address, factor, signed)
} else {
val operandReg = codeGen.vmRegisters.nextFree()
code += translateExpression(operand, operandReg, -1)
code += if(signed)
VmCodeInstruction(Opcode.DIVSM, vmDt, reg1=operandReg, value = address)
else
VmCodeInstruction(Opcode.DIVM, vmDt, reg1=operandReg, value = address)
}
}
return code
}
private fun operatorMultiply(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
val constFactorLeft = binExpr.left as? PtNumber
val constFactorRight = binExpr.right as? PtNumber
if(vmDt==VmDataType.FLOAT) {
if(constFactorLeft!=null) {
code += translateExpression(binExpr.right, -1, resultFpRegister)
val factor = constFactorLeft.number.toFloat()
code += codeGen.multiplyByConstFloat(resultFpRegister, factor)
} else if(constFactorRight!=null) {
code += translateExpression(binExpr.left, -1, resultFpRegister)
val factor = constFactorRight.number.toFloat()
code += codeGen.multiplyByConstFloat(resultFpRegister, factor)
} else {
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(binExpr.left, -1, resultFpRegister)
code += translateExpression(binExpr.right, -1, rightResultFpReg)
code += VmCodeInstruction(Opcode.MUL, vmDt, fpReg1 = resultFpRegister, fpReg2 = rightResultFpReg)
}
} else {
if(constFactorLeft!=null && constFactorLeft.type!=DataType.FLOAT) {
code += translateExpression(binExpr.right, resultRegister, -1)
val factor = constFactorLeft.number.toInt()
code += codeGen.multiplyByConst(vmDt, resultRegister, factor)
} else if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
code += translateExpression(binExpr.left, resultRegister, -1)
val factor = constFactorRight.number.toInt()
code += codeGen.multiplyByConst(vmDt, resultRegister, factor)
} else {
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
code += VmCodeInstruction(Opcode.MUL, vmDt, reg1=resultRegister, reg2=rightResultReg)
}
}
return code
}
internal fun operatorMultiplyInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
val code = VmCodeChunk()
val constFactorRight = operand as? PtNumber
if(vmDt==VmDataType.FLOAT) {
if(constFactorRight!=null) {
val factor = constFactorRight.number.toFloat()
code += codeGen.multiplyByConstFloatInplace(address, factor)
} else {
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(operand, -1, operandFpReg)
code += VmCodeInstruction(Opcode.MULM, vmDt, fpReg1 = operandFpReg, value = address)
}
} else {
if(constFactorRight!=null && constFactorRight.type!=DataType.FLOAT) {
val factor = constFactorRight.number.toInt()
code += codeGen.multiplyByConstInplace(vmDt, address, factor)
} else {
val operandReg = codeGen.vmRegisters.nextFree()
code += translateExpression(operand, operandReg, -1)
code += VmCodeInstruction(Opcode.MULM, vmDt, reg1=operandReg, value = address)
}
}
return code
}
private fun operatorMinus(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
if(vmDt==VmDataType.FLOAT) {
if((binExpr.right as? PtNumber)?.number==1.0) {
code += translateExpression(binExpr.left, -1, resultFpRegister)
code += VmCodeInstruction(Opcode.DEC, vmDt, fpReg1 = resultFpRegister)
}
else {
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(binExpr.left, -1, resultFpRegister)
code += translateExpression(binExpr.right, -1, rightResultFpReg)
code += VmCodeInstruction(Opcode.SUB, vmDt, fpReg1=resultFpRegister, fpReg2=rightResultFpReg)
}
} else {
if((binExpr.right as? PtNumber)?.number==1.0) {
code += translateExpression(binExpr.left, resultRegister, -1)
code += VmCodeInstruction(Opcode.DEC, vmDt, reg1=resultRegister)
}
else {
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
code += VmCodeInstruction(Opcode.SUB, vmDt, reg1=resultRegister, reg2=rightResultReg)
}
}
return code
}
internal fun operatorMinusInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
val code = VmCodeChunk()
if(vmDt==VmDataType.FLOAT) {
if((operand as? PtNumber)?.number==1.0) {
code += VmCodeInstruction(Opcode.DECM, vmDt, value=address)
}
else {
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(operand, -1, operandFpReg)
code += VmCodeInstruction(Opcode.SUBM, vmDt, fpReg1=operandFpReg, value=address)
}
} else {
if((operand as? PtNumber)?.number==1.0) {
code += VmCodeInstruction(Opcode.DECM, vmDt, value=address)
}
else {
val operandReg = codeGen.vmRegisters.nextFree()
code += translateExpression(operand, operandReg, -1)
code += VmCodeInstruction(Opcode.SUBM, vmDt, reg1=operandReg, value = address)
}
}
return code
}
private fun operatorPlus(binExpr: PtBinaryExpression, vmDt: VmDataType, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
val code = VmCodeChunk()
if(vmDt==VmDataType.FLOAT) {
if((binExpr.left as? PtNumber)?.number==1.0) {
code += translateExpression(binExpr.right, -1, resultFpRegister)
code += VmCodeInstruction(Opcode.INC, vmDt, fpReg1=resultFpRegister)
}
else if((binExpr.right as? PtNumber)?.number==1.0) {
code += translateExpression(binExpr.left, -1, resultFpRegister)
code += VmCodeInstruction(Opcode.INC, vmDt, fpReg1=resultFpRegister)
}
else {
val rightResultFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(binExpr.left, -1, resultFpRegister)
code += translateExpression(binExpr.right, -1, rightResultFpReg)
code += VmCodeInstruction(Opcode.ADD, vmDt, fpReg1=resultFpRegister, fpReg2=rightResultFpReg)
}
} else {
if((binExpr.left as? PtNumber)?.number==1.0) {
code += translateExpression(binExpr.right, resultRegister, -1)
code += VmCodeInstruction(Opcode.INC, vmDt, reg1=resultRegister)
}
else if((binExpr.right as? PtNumber)?.number==1.0) {
code += translateExpression(binExpr.left, resultRegister, -1)
code += VmCodeInstruction(Opcode.INC, vmDt, reg1=resultRegister)
}
else {
val rightResultReg = codeGen.vmRegisters.nextFree()
code += translateExpression(binExpr.left, resultRegister, -1)
code += translateExpression(binExpr.right, rightResultReg, -1)
code += VmCodeInstruction(Opcode.ADD, vmDt, reg1=resultRegister, reg2=rightResultReg)
}
}
return code
}
internal fun operatorPlusInplace(address: Int, vmDt: VmDataType, operand: PtExpression): VmCodeChunk {
val code = VmCodeChunk()
if(vmDt==VmDataType.FLOAT) {
if((operand as? PtNumber)?.number==1.0) {
code += VmCodeInstruction(Opcode.INCM, vmDt, value = address)
}
else {
val operandFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(operand, -1, operandFpReg)
code += VmCodeInstruction(Opcode.ADDM, vmDt, fpReg1=operandFpReg, value=address)
}
} else {
if((operand as? PtNumber)?.number==1.0) {
code += VmCodeInstruction(Opcode.INCM, vmDt, value = address)
}
else {
val operandReg = codeGen.vmRegisters.nextFree()
code += translateExpression(operand, operandReg, -1)
code += VmCodeInstruction(Opcode.ADDM, vmDt, reg1=operandReg, value=address)
}
}
return code
}
fun translate(fcall: PtFunctionCall, resultRegister: Int, resultFpRegister: Int): VmCodeChunk {
val subroutine = codeGen.symbolTable.flat.getValue(fcall.functionName) as StSub
val code = VmCodeChunk()
for ((arg, parameter) in fcall.args.zip(subroutine.parameters)) {
val paramDt = codeGen.vmType(parameter.type)
if(codeGen.isZero(arg)) {
if (paramDt == VmDataType.FLOAT) {
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
code += VmCodeInstruction(Opcode.STOREZM, paramDt, value = mem)
} else {
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
code += VmCodeInstruction(Opcode.STOREZM, paramDt, value = mem)
}
} else {
if (paramDt == VmDataType.FLOAT) {
val argFpReg = codeGen.vmRegisters.nextFreeFloat()
code += translateExpression(arg, -1, argFpReg)
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
code += VmCodeInstruction(Opcode.STOREM, paramDt, fpReg1 = argFpReg, value = mem)
} else {
val argReg = codeGen.vmRegisters.nextFree()
code += translateExpression(arg, argReg, -1)
val mem = codeGen.allocations.get(fcall.functionName + parameter.name)
code += VmCodeInstruction(Opcode.STOREM, paramDt, reg1 = argReg, value = mem)
}
}
}
code += VmCodeInstruction(Opcode.CALL, labelSymbol=fcall.functionName)
if(fcall.type==DataType.FLOAT) {
if (!fcall.void && resultFpRegister != 0) {
// Call convention: result value is in fr0, so put it in the required register instead.
code += VmCodeInstruction(Opcode.LOADR, VmDataType.FLOAT, fpReg1 = resultFpRegister, fpReg2 = 0)
}
} else {
if (!fcall.void && resultRegister != 0) {
// Call convention: result value is in r0, so put it in the required register instead.
code += VmCodeInstruction(Opcode.LOADR, codeGen.vmType(fcall.type), reg1 = resultRegister, reg2 = 0)
}
}
return code
}
}

View File

@ -0,0 +1,91 @@
package prog8.codegen.virtual
import prog8.code.SymbolTable
import prog8.code.ast.PtProgram
import prog8.code.core.*
class VariableAllocator(private val st: SymbolTable, private val program: PtProgram, errors: IErrorReporter) {
private val allocations = mutableMapOf<List<String>, Int>()
private var freeMemoryStart: Int
val freeMem: Int
get() = freeMemoryStart
init {
var nextLocation = 0
for (variable in st.allVariables) {
val memsize =
when (variable.dt) {
DataType.STR -> variable.initialStringValue!!.first.length + 1 // include the zero byte
in NumericDatatypes -> program.memsizer.memorySize(variable.dt)
in ArrayDatatypes -> program.memsizer.memorySize(variable.dt, variable.length!!)
else -> throw InternalCompilerException("weird dt")
}
allocations[variable.scopedName] = nextLocation
nextLocation += memsize
}
freeMemoryStart = nextLocation
}
fun get(name: List<String>) = allocations.getValue(name)
fun asVmMemory(): List<Pair<List<String>, String>> {
val mm = mutableListOf<Pair<List<String>, String>>()
for (variable in st.allVariables) {
val location = allocations.getValue(variable.scopedName)
val typeStr = when(variable.dt) {
DataType.UBYTE, DataType.ARRAY_UB, DataType.STR -> "ubyte"
DataType.BYTE, DataType.ARRAY_B -> "byte"
DataType.UWORD, DataType.ARRAY_UW -> "uword"
DataType.WORD, DataType.ARRAY_W -> "word"
DataType.FLOAT, DataType.ARRAY_F -> "float"
else -> throw InternalCompilerException("weird dt")
}
val value = when(variable.dt) {
DataType.FLOAT -> (variable.initialNumericValue ?: 0.0).toString()
in NumericDatatypes -> (variable.initialNumericValue ?: 0).toHex()
DataType.STR -> {
val encoded = program.encoding.encodeString(variable.initialStringValue!!.first, variable.initialStringValue!!.second)
encoded.joinToString(",") { it.toInt().toHex() } + ",0"
}
DataType.ARRAY_F -> {
if(variable.initialArrayValue!=null) {
variable.initialArrayValue!!.joinToString(",") { it.number!!.toString() }
} else {
(1..variable.length!!).joinToString(",") { "0" }
}
}
in ArrayDatatypes -> {
if(variable.initialArrayValue!==null) {
variable.initialArrayValue!!.joinToString(",") { it.number!!.toHex() }
} else {
(1..variable.length!!).joinToString(",") { "0" }
}
}
else -> throw InternalCompilerException("weird dt")
}
mm.add(Pair(variable.scopedName, "$location $typeStr $value"))
}
return mm
}
private val memorySlabsInternal = mutableMapOf<String, Triple<UInt, UInt, UInt>>()
internal val memorySlabs: Map<String, Triple<UInt, UInt, UInt>> = memorySlabsInternal
fun allocateMemorySlab(name: String, size: UInt, align: UInt): UInt {
val address =
if(align==0u || align==1u)
freeMemoryStart.toUInt()
else
(freeMemoryStart.toUInt() + align-1u) and (0xffffffffu xor (align-1u))
memorySlabsInternal[name] = Triple(address, size, align)
freeMemoryStart = (address + size).toInt()
return address
}
fun getMemorySlab(name: String): Triple<UInt, UInt, UInt>? = memorySlabsInternal[name]
}

View File

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

View File

@ -1,41 +0,0 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}

View File

@ -1,46 +0,0 @@
package prog8.compiler.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.compiler.target.cbm.Petscii
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.compiler.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compiler.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
val coded= if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}

View File

@ -1,206 +0,0 @@
package prog8.compiler.target.c64
import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
import kotlin.math.absoluteValue
import kotlin.math.pow
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 = 0x0801u
override val RAW_LOAD_ADDRESS = 0xc000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
if(selectedEmulator!=1) {
System.err.println("The c64 target only supports the main emulator (Vice).")
return
}
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "${programNameWithPath}.$viceMonListPostfix",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", "${programNameWithPath}.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 isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
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")
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x02u // temp storage for a single byte
override val SCRATCH_REG = 0x03u // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xfbu // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfdu // temp storage 2 for a word $fb+$fc
init {
if (options.floats && options.zeropage !in arrayOf(ZeropageType.FLOATSAFE, ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw InternalCompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe' or 'dontuse'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04u..0xf9u)
free.add(0xffu)
free.removeAll(setOf(0xa0u, 0xa1u, 0xa2u, 0x91u, 0xc0u, 0xc5u, 0xcbu, 0xf5u, 0xf6u)) // 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 'kernal work storage area'
).map{it.toUInt()})
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zeropage locations used for floating point operations from the free list
free.removeAll(listOf(
0x22, 0x23, 0x24, 0x25,
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
).map{it.toUInt()})
}
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).map{it.toUInt()})
} else {
// don't use the zeropage at all
free.clear()
}
}
removeReservedFromFreePool()
}
}
data class Mflpt5(val b0: UByte, val b1: UByte, val b2: UByte, val b3: UByte, val b4: UByte):
IMachineFloat {
companion object {
val zero = Mflpt5(0u, 0u, 0u, 0u, 0u)
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 InternalCompilerException("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 InternalCompilerException("floating point overflow: $this")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toUByte(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toUByte(),
(mantLong.and(0x00ff0000L) ushr 16).toUByte(),
(mantLong.and(0x0000ff00L) ushr 8).toUByte(),
(mantLong.and(0x000000ffL)).toUByte())
}
}
}
}
override fun toDouble(): Double {
if (this == zero) return 0.0
val exp = b0.toInt() - 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,103 +0,0 @@
package prog8.compiler.target.cbm
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IAssemblyProgram
import prog8.compilerinterface.OutputType
import prog8.compilerinterface.generatedLabelPrefix
import prog8.parser.SourceCode
import java.io.File
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.isRegularFile
internal const val viceMonListPostfix = "vice-mon-list"
class AssemblyProgram(
override val valid: Boolean,
override val name: String,
outputDir: Path,
private val compTarget: String) : 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.$viceMonListPostfix")
override fun assemble(quiet: Boolean, options: CompilationOptions): Int {
// 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",
"--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
if(quiet)
command.add("--quiet")
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating prg for target $compTarget.")
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target $compTarget.")
binFile
}
}
command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
val proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor()
if (result == 0) {
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
return result
}
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 match = pattern.matchEntire(line)
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")
}
}
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
return com.github.michaelbull.result.runCatching {
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").readText()
}.mapError { NoSuchFileException(File(filename)) }
} else {
val sib = Path(source.origin).resolveSibling(filename)
if (sib.isRegularFile())
Ok(SourceCode.File(sib).readText())
else
Ok(SourceCode.File(Path(filename)).readText())
}
}

View File

@ -1,124 +0,0 @@
package prog8.compiler.target.cx16
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.cbm.viceMonListPostfix
import prog8.compilerinterface.*
import java.io.IOException
import java.nio.file.Path
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 = 0x0801u
override val RAW_LOAD_ADDRESS = 0x8000u
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
override val ESTACK_LO = 0x0400u // $0400-$04ff inclusive
override val ESTACK_HI = 0x0500u // $0500-$05ff inclusive
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path) {
val emulatorName: String
val extraArgs: List<String>
when(selectedEmulator) {
1 -> {
emulatorName = "x16emu"
extraArgs = emptyList()
}
2 -> {
emulatorName = "box16"
extraArgs = listOf("-sym", "${programNameWithPath}.$viceMonListPostfix")
}
else -> {
System.err.println("Cx16 target only supports x16emu and box16 emulators.")
return
}
}
for(emulator in listOf(emulatorName)) {
println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "${programNameWithPath}.prg") + extraArgs
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 isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
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")
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
init {
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE ))
throw InternalCompilerException("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(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
}
removeReservedFromFreePool()
}
}
}

View File

@ -1,8 +0,0 @@
package prog8tests.asmgen
import io.kotest.core.config.AbstractProjectConfig
import kotlin.math.max
object ProjectConfig : AbstractProjectConfig() {
override val parallelism = max(2, Runtime.getRuntime().availableProcessors() / 2)
}

View File

@ -1,37 +0,0 @@
package prog8tests.asmgen.helpers
import prog8.ast.IBuiltinFunctions
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.compilerinterface.IMemSizer
import prog8.ast.base.DataType
import prog8.compilerinterface.IStringEncoding
internal val DummyFunctions = object : IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(
name: String,
args: List<Expression>,
position: Position,
): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
internal val DummyMemsizer = object : IMemSizer {
override fun memorySize(dt: DataType) = 0
}
internal val DummyStringEncoder = object : IStringEncoding {
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
return emptyList()
}
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean): String {
return ""
}
}

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