Compare commits

..

455 Commits
v4.6 ... v6.3

Author SHA1 Message Date
e4bca5fe47 version 2021-03-06 23:07:30 +01:00
a1729b65ab fix min(), max(), sum(), abs() 2021-03-06 22:57:22 +01:00
2950d26c8e array and struct value assignments now via memcopy instead of assignment per element 2021-03-06 22:10:03 +01:00
4f8d4a9585 use memcopy to assign arrays 2021-03-06 19:01:16 +01:00
d787795759 simplified 2021-03-06 15:43:23 +01:00
cf74e73e27 IDEA syntax colors 2021-03-06 15:23:58 +01:00
2770254fd9 removed inline assembly from bobs demo 2021-03-06 14:31:26 +01:00
de04bd8cfa added more convenient number-to-string functions to conv library 2021-03-06 13:47:27 +01:00
076a547f91 added more convenient number-to-string functions to conv library 2021-03-06 13:34:57 +01:00
dffd0a2706 added fastrnd8() with the old rnd() generator code in it, new code for rnd() uses the much better rndw() generator now. 2021-03-05 22:49:14 +01:00
6c66f86103 todo 2021-03-05 21:07:35 +01:00
26502c949a add unlimited bobs example 2021-03-05 19:05:13 +01:00
8dfe510883 avoid compiler crash when evaluating const expressions fails due to things like integer out of bounds 2021-03-04 01:32:02 +01:00
96ba9f5902 spelling correction 2021-03-04 01:31:29 +01:00
3a6ba0ab71 added 'kefrenbars' example 2021-03-03 01:09:18 +01:00
32d894d6b6 optimized repeat loop for word counts 2021-02-28 21:22:46 +01:00
543efa4299 attempt 2 at optimizing repeats 2021-02-28 21:02:17 +01:00
eba0708099 Revert "optimized repeat loop for word counts"
This reverts commit 51e6bf0d
2021-02-28 20:29:28 +01:00
51e6bf0d45 optimized repeat loop for word counts 2021-02-28 17:34:18 +01:00
07b5c44a54 preparing to optimize 16 bit repeat loop 2021-02-28 17:13:15 +01:00
9fe32c1c34 codegen uses 'bra' on 65c02 instead of 'jmp' 2021-02-28 16:46:08 +01:00
0e0278c84a for loops now use 'bra' if available 2021-02-28 16:35:59 +01:00
dea775a9cd package refactor 2021-02-28 16:29:15 +01:00
7e3e18a5c7 deal with 'bra' better on 65c02 2021-02-28 16:20:03 +01:00
8e3ebc84f0 readme 2021-02-28 15:40:04 +01:00
e6079dfd71 don't always use pha/pla in pointer expression code 2021-02-27 16:21:46 +01:00
2b435fe6a5 vtui example updated to vtui 0.6 2021-02-27 03:30:21 +01:00
4e640b11fd added kernal bank switch trick to rasterbars 2021-02-26 01:16:06 +01:00
8b1e1e68fa switch to Kotlin's new JVM IR compilation 2021-02-26 01:10:00 +01:00
fd11927708 optimized highres 4c position calc a bit 2021-02-26 00:43:51 +01:00
cd500fee8c wording 2021-02-25 00:52:27 +01:00
1bd32c0f19 added animal guessing game example 2021-02-24 22:58:16 +01:00
7aefca3de0 target 2021-02-24 00:17:52 +01:00
f275ed96ea optimized palette.set_color() 2021-02-24 00:01:27 +01:00
d14dac3872 got rid of final traces of heapid, fixed compiler warnings 2021-02-24 00:01:04 +01:00
b0213b0565 vtui lib 2021-02-23 23:31:32 +01:00
c677f0a875 fixed string interning to also consider the alt-encoding 2021-02-23 23:27:44 +01:00
6e65cb2c0a added sounds to cx16 tehtriz 2021-02-23 01:29:45 +01:00
e65c5402d7 added cx16 rasterbars example 2021-02-22 02:11:44 +01:00
334f86480a added irq routines for cx16 2021-02-22 00:48:41 +01:00
0e62f5b759 don't remove subroutines in a block marked with "force_output" 2021-02-21 23:25:26 +01:00
edf9a500d3 kernel -> kernal 2021-02-21 22:48:06 +01:00
001d01fdaf slight tweak to 64tass .cpu to enable wdc65c02 variant on cx16 with its extra opcodes 2021-02-21 22:45:23 +01:00
a95677564e changed system irq/rasterirq setting routines 2021-02-21 22:23:50 +01:00
4aca8bb8df also track subroutines in the callgraph that only get their address taken 2021-02-21 22:09:49 +01:00
5540482888 compiler error for duplicate when choice labels 2021-02-21 21:26:15 +01:00
00d735249b fix pointer write outside zeropage 2021-02-21 16:22:44 +01:00
b5289511ba don't remove empty when choice from the list of choices! 2021-02-21 15:11:19 +01:00
b6ded8501f added 'align_word' and 'align_page' block options to control block start address alignment in the assembler 2021-02-21 01:24:44 +01:00
781915d2cf reducing dependencies 2021-02-20 17:54:33 +01:00
f4cef3eaf2 reducing dependencies 2021-02-20 17:19:54 +01:00
d23c2eed86 test 2021-02-20 16:58:24 +01:00
15695a304e start address of blocks without explicit memory address, is now word-aligned in memory 2021-02-20 03:06:00 +01:00
6319269976 underscore '_' is now also mapped to petscii, to the graphical symbol 2021-02-20 02:55:06 +01:00
0ed3d951a7 don't require carry parameter Pc to asmsubs to be last 2021-02-20 02:27:57 +01:00
2aa39757b4 reduce dependencies on global compilationtarget 2021-02-19 19:02:29 +01:00
39d32a3600 refactor cpuCheck 2021-02-19 18:48:12 +01:00
219d17de34 reduce dependencies on global compilaiontarget 2021-02-19 18:33:54 +01:00
9bb5b454e4 reduce dependencies on global compilaiontarget 2021-02-18 23:44:26 +01:00
2412f8c531 added cx16 vtui example 2021-02-18 23:16:38 +01:00
8701d684e6 added cx16 vtui example 2021-02-18 03:45:06 +01:00
b543cc34cd no longer warn about removing unused asmsubs 2021-02-18 01:52:56 +01:00
791dbbab9b fixed block label itself not getting the correct memory address in the assembly
fixed %asmbinary relative path issues
2021-02-18 01:28:33 +01:00
ac0b1da3fc machinedefinition doesn't import system libs itself anymore 2021-02-18 00:43:32 +01:00
2f97aedc3c fixed invalid removal of string tag from memory() 2021-02-16 23:58:31 +01:00
ab544ee965 improved string constant interning; no longer output duplicate strings in the Ast 2021-02-16 23:43:38 +01:00
fa527f8624 restored optimization of txt.print() with strings of lengths 1 or 2 2021-02-16 23:37:11 +01:00
92ee0aefee docs: replaced old invalid c64scr names with txt 2021-02-16 23:28:35 +01:00
99759ae853 enhanced tehtriz blocks to have light edges 2021-02-15 17:48:10 +01:00
81930312ff added textio.setcc2() on commanderX16 to enable setting fg+bg colors. 2021-02-15 17:47:48 +01:00
194fbcdd91 todos 2021-02-15 04:41:33 +01:00
1e3930aae2 fix bug in evaluating logical expressions if one of the operands was not boolean 1 or 0 2021-02-14 18:29:05 +01:00
62dda4d891 fix asm bug in conv.any2uword 2021-02-14 17:13:56 +01:00
2b870fb9f7 get rid of compiled examples. Just compile them yourself... 2021-02-14 17:13:29 +01:00
53f0318187 version 6.1 2021-02-14 00:07:45 +01:00
5e6e711f33 optimize pokew() 2021-02-14 00:05:57 +01:00
78af2cd4dc optimize peekw() 2021-02-13 23:52:08 +01:00
02cb237623 added poke() and pokew() builtin functions 2021-02-13 23:16:50 +01:00
cc0f19653e added peek() and peekw() builtin functions 2021-02-13 22:38:39 +01:00
4fff150c7b fixed mkword() bug 2021-02-13 22:00:13 +01:00
f6136891cc optimized for loop over const bytes, fixed downto 1 2021-02-13 13:46:02 +01:00
1e22170302 added graphical starmaps to textelite 2021-02-11 00:23:36 +01:00
bdda6f502a textelite output cleanups and alignments 2021-02-10 23:19:07 +01:00
1bbd77fddb added txt.column() 2021-02-10 22:47:49 +01:00
321fdd10d1 ported tehtriz to Cx16 2021-02-10 21:55:14 +01:00
9867dfcdeb ported tehtriz to Cx16 2021-02-10 21:44:35 +01:00
7c09ac632c got rid of the --longOptionNames in the cli argparser 2021-02-10 21:26:46 +01:00
3502f65332 reducing dependencies 2021-02-09 19:03:21 +01:00
628390c3b5 reducing dependencies 2021-02-09 18:56:47 +01:00
bc37097df2 reducing dependencies 2021-02-09 18:49:25 +01:00
7d98275763 reducing dependencies 2021-02-09 02:06:27 +01:00
d6ffb549f6 reducing dependencies 2021-02-09 01:47:05 +01:00
bcd0db984d reducing ast dependencies - moved ErrorReporter back to compiler module 2021-02-09 01:15:31 +01:00
d9244f22c2 reducing ast dependencies - separate Ast compilation module 2021-02-09 01:06:11 +01:00
c97d76dbf2 reducing ast dependencies 2021-02-09 00:05:56 +01:00
9e05e97d7f reducing ast dependencies 2021-02-07 19:38:20 +01:00
1070dedd7c todo 2021-02-07 19:08:47 +01:00
ccd1516637 reducing ast dependencies 2021-02-07 18:44:38 +01:00
f1f51a01c6 reducing ast dependencies 2021-02-07 18:34:55 +01:00
be75b8dbe5 reducing ast dependencies 2021-02-07 07:05:00 +01:00
02fae0e722 reducing ast dependencies 2021-02-07 06:50:59 +01:00
e35b739579 reducing ast dependencies 2021-02-07 06:39:08 +01:00
34aa6cc8a2 compiler checks for conflicting register usage in sub arguments vs target parameter registers 2021-02-07 05:25:50 +01:00
d7a6b20028 todo 2021-02-07 01:14:10 +01:00
eb2d5bb1f8 fix bank arg error in gfx2.position 2021-02-06 16:58:17 +01:00
cefef3d1be todo 2021-02-06 15:22:31 +01:00
cc96ab7a9b assignment source now also treats cx16.r[0-15] as registers
no longer create useless assignment code for r0=r0
2021-02-06 13:01:45 +01:00
49ea31c0a4 fix shift signed word right 2021-02-06 01:23:31 +01:00
f1478d776b fix vertical line highres 4color 2021-02-05 18:09:21 +01:00
40e4cfb686 amiga 2021-02-04 17:47:52 +01:00
76f459ee95 amiga 2021-02-02 23:09:03 +01:00
c478718019 fixed and optimized horiz_line for highres 4c 2021-02-01 22:03:10 +01:00
c27248a58b amiga 2021-01-29 23:52:29 +01:00
51bc539468 added palette.set_rgb() 2021-01-29 02:46:07 +01:00
2395863e7e asmsubs: fix clobbering and optimize register usage for loading the arguments 2021-01-29 01:52:49 +01:00
69c459c8ac gfx2 highres 4colors 2021-01-28 22:28:14 +01:00
c8855b2b10 better error msg 2021-01-27 02:40:56 +01:00
a910c0fddb gfx2 highres 4colors 2021-01-27 02:31:20 +01:00
fd55611cac asmsubs: don't use stack for simple lsb/msb/mkword arguments 2021-01-27 01:41:55 +01:00
52f6be2bb0 gfx2: changed screen mode numbering to a more intuitive sequence 2021-01-26 18:17:20 +01:00
857f930dc2 amiga 2021-01-26 00:09:42 +01:00
dd2c436dc6 tweaked repeat 2021-01-25 23:39:54 +01:00
9f047ba752 palette.set_monochrome() now has 2 arguments: screen and draw color RGB values 2021-01-24 04:15:15 +01:00
9d4ec4a9b2 syntaxfile 2021-01-24 00:42:26 +01:00
cdc6d9aa65 moved cx16 imageviewer into its own git repo. Version 6.0. 2021-01-23 23:49:17 +01:00
997bc21feb added offsetof() to get the byte offset of struct members. 2021-01-23 23:11:57 +01:00
975af4764d remove no longer needed strlen() calls from diskio routines 2021-01-23 22:46:46 +01:00
bf69219f98 allow uwordpointer[index] syntax as equivalent to @(uwordpointer+index) index can be >255 here! 2021-01-23 22:39:30 +01:00
f34f9329f1 fixed bug in memcopy 2021-01-23 19:49:53 +01:00
90271d0dcd textelite was okay 2021-01-23 19:01:02 +01:00
195cd7597d fix pointer-to-pointer assignment 2021-01-23 18:50:46 +01:00
4a81406262 fix diskio rename() and delete() 2021-01-23 17:57:30 +01:00
f9fd426843 Merge branch 'pointer-index-optimize'
# Conflicts:
#	docs/source/todo.rst
2021-01-23 15:57:23 +01:00
e612056ecd more optimal screen pointer access in plasma.p8 example 2021-01-23 15:54:18 +01:00
6f0103398b fix Y register clobbering in pointer access code 2021-01-23 15:24:41 +01:00
afb60db382 todo 2021-01-20 18:43:08 +01:00
5731b876ff textelite save bug found 2021-01-20 01:36:46 +01:00
055f917a2e fixed missing code for certain memread expressions when casted to uword 2021-01-20 01:30:11 +01:00
4ed7fb771c started pointer access optimization 2021-01-20 00:17:33 +01:00
c328e9018c cx16 assembler was moved into its own github repo 2021-01-18 01:38:33 +01:00
b270f6f713 added cx16.rombank() and rambank(). Select kernal rom in i/o heavy programs for faster disk i/o 2021-01-17 19:16:21 +01:00
5c13918f11 cx16 reset_system() bank selection change 2021-01-17 18:28:43 +01:00
40cc216557 optimize pointer var access if var is already on zeropage 2021-01-16 18:31:37 +01:00
1481f92cb0 optimize memory read expression of ptr + constant index 2021-01-16 17:41:15 +01:00
76d54fbe5c optimize assignment to memory pointer with fixed byte offset 2021-01-15 20:46:47 +01:00
9f72779cdc optimize assignment from memory pointer with fixed byte offset 2021-01-15 20:09:13 +01:00
3dcef89a74 optimize (zp),y instructions for 65c02 to use (zp) 2021-01-15 19:14:35 +01:00
46373717b6 get rid of unused ci image format reader 2021-01-15 18:29:25 +01:00
7277c08fa6 added textio.spc(). assem tweaks. 2021-01-14 22:51:09 +01:00
04e75455c4 assem tweaks 2021-01-14 21:07:06 +01:00
8ac17ae14e fix assem parsing of 4 letter instructions 2021-01-14 18:41:29 +01:00
cb5d6ddf80 readme 2021-01-13 22:48:59 +01:00
e0794db33a version 6.0beta 2021-01-13 22:41:11 +01:00
b128b79132 clearer description of memory() 2021-01-13 22:32:17 +01:00
79e6d4b8dd better check for EOF status 2021-01-13 22:11:51 +01:00
b9ddde0f12 assem 2021-01-12 03:45:18 +01:00
a0ec37b35b compiler error for missing return value 2021-01-10 16:36:08 +01:00
506ac8014c fix diskio.f_readline() that skipped first char. It also doesn't leave the end of line char in the string now. 2021-01-10 16:21:25 +01:00
72b4198301 added string.lower() / string.upper() 2021-01-10 15:29:43 +01:00
24eee0cb34 lower 2021-01-10 15:15:00 +01:00
9fc0c3f849 removed diskio.f_read_exact() - wasn't worth it over f_read() 2021-01-10 14:29:51 +01:00
db314ed903 added diskio.f_readline() 2021-01-10 05:04:56 +01:00
1ef9b8be61 assem 2021-01-10 03:44:10 +01:00
79782ad547 conv.any2uword() changed return value 2021-01-08 22:43:01 +01:00
4b6d045df1 idea syntaxfile updated 2021-01-08 16:57:16 +01:00
b4d1d545a8 introduced txt.nl() 2021-01-08 16:56:17 +01:00
f61682cdc7 moved various miscellaneous builtin functions such as exit() and progend() to sys.* 2021-01-08 16:44:34 +01:00
d61420f1c6 oops 2021-01-08 01:31:28 +01:00
3d09d605e1 moved memcopy, memset, memsetw builtin functions to sys.* 2021-01-08 01:09:37 +01:00
025dde264a move target() builtin to sys.target constant 2021-01-07 23:36:28 +01:00
87cee7a0fd check for name conflict with existing block (/module) 2021-01-07 23:28:15 +01:00
61784a03bb removed all string related builtin functions and moved them to separate routines in new 'string' library module 2021-01-07 23:10:29 +01:00
9d9ca0f08d fix bit shifting words by 8. fix type error for signed return types. 2021-01-07 22:50:40 +01:00
58f37513e7 removed all string related builtin functions and moved them to separate routines in new 'string' library module 2021-01-07 20:01:11 +01:00
ee7f9d457d text editor configs 2021-01-07 01:56:31 +01:00
bec2224c3d clearer naming 2021-01-07 01:25:50 +01:00
4305984168 assem 2021-01-06 01:03:08 +01:00
07dd64958f conv.bin2uword, conv.hex2uword, conv.str2uword, conv.str2word more robust and return parsed length in cx16.r15 2021-01-06 00:11:15 +01:00
76101d7f8d assem 2021-01-05 22:56:52 +01:00
7d6a0ab256 added conv.any2uword() 2021-01-05 22:28:46 +01:00
4309a0dc68 assem 2021-01-05 04:46:25 +01:00
dde6919446 allow when choice values to be replaced in ast (const-folding) 2021-01-05 03:49:11 +01:00
54fc9c91ac fix hole in scratch zp allocation of cx16 2021-01-05 03:48:36 +01:00
41658c97a3 assem 2021-01-05 02:49:29 +01:00
45c9cc97d9 fix invalid handling of X register functioncall result value 2021-01-05 02:44:55 +01:00
6fa7debee5 todo 2021-01-05 02:17:51 +01:00
ee9f662016 added MEMTOP2 pseudo kernal routine on cx16 to get the number of RAM banks 2021-01-05 01:48:23 +01:00
3550e1214c fix invalid handling of X register functioncall result value 2021-01-05 01:42:51 +01:00
8dcb43ad1c assem 2021-01-04 20:15:07 +01:00
e6a1442296 sys.wait() no longer resets the jiffyclock to zero 2021-01-03 02:45:25 +01:00
cb65480c6c moved wait() and reset_system() to sys block so they are now unified across c64 and cx16 2021-01-03 02:36:45 +01:00
3e7c7ab497 assem optimize 4 letter mnems for size 2021-01-03 02:17:35 +01:00
f0930d8a18 added c64.RDTIM16() utility routine to just get the lower 16 bits of the jiffy clock 2021-01-02 20:59:48 +01:00
5a846bdeb5 fixed invalid integer constant expression evaluation leading to wrong results 2021-01-02 20:33:59 +01:00
baf9dfb46c assem 2021-01-02 20:33:07 +01:00
edd3a22848 added strfind() 2021-01-02 17:49:58 +01:00
583428b19c assem 2021-01-02 15:40:36 +01:00
08d44ae553 fix compiler errors 2021-01-02 15:40:24 +01:00
b3b2541c1e assem 2021-01-01 19:25:40 +01:00
8e927e0b73 nontrivial return value evaluation now via intermediary variable to try to avoid slow stack based evaluation 2020-12-31 22:13:24 +01:00
8e3e996f4a diskio.f_open() now also checks if file exists 2020-12-31 19:27:34 +01:00
b6fa361bcc exit() now also resets the io channels. Optimized diskio data read subroutines. added diskio.f_read_all() 2020-12-31 19:09:29 +01:00
ca83092aed added large example program to check / profile compiler performance 2020-12-31 01:10:48 +01:00
3cda92331e updated dirlist 2020-12-31 01:07:37 +01:00
c989abe265 optimize ubyte -> uword type casts more 2020-12-31 01:02:36 +01:00
89230ade7a change in pattern arguments of diskio.list_files() and lf_start_list(): you can now use a simple pattern with ? and * wildcards 2020-12-30 23:34:00 +01:00
b4931c9a1f optimize horzontal_line drawing 2020-12-30 18:58:47 +01:00
ddfcf45d40 added some missing clobbers() specs 2020-12-30 16:59:31 +01:00
ee12236d53 added rect functions 2020-12-30 00:53:13 +01:00
df6698c98f fixed circle and disc geometry 2020-12-30 00:11:42 +01:00
c3b82f2cfa optimized disc() 2020-12-29 23:58:11 +01:00
64c89f1c8f fix circle and disc geometry, added rect and line routines 2020-12-29 23:52:48 +01:00
e09b65ea94 fix gfx2 vertical_line 2020-12-29 23:07:26 +01:00
c81952c356 gfx2 optimizations for vertical lines 2020-12-29 02:13:38 +01:00
f80e462d25 gfx2 optimizations for vertical lines 2020-12-29 01:36:34 +01:00
51f32677b7 gfx2 optimizations for horizontal lines, fix bug in disc drawing 2020-12-29 01:23:14 +01:00
4b366358c4 fix gfx2 color of horiz/vert lines 2020-12-28 01:33:51 +01:00
3378586098 update gradle to 6.7 2020-12-28 00:46:30 +01:00
6777d952c1 fixed crash when loopvar in for loop wasn't defined 2020-12-28 00:30:08 +01:00
6c8b18ddbd fixed crash on cx16 in word to float conversion 2020-12-28 00:19:58 +01:00
69780ecde9 fixed % operator bug 2020-12-28 00:08:22 +01:00
9e2c52e1ec added Cx16 highresbitmap example. added stippled drawing to gfx2 monochrome mode 2020-12-27 23:57:13 +01:00
6cb0e6a936 fixed lsb(value) not working when used in a comparison expression (needed to flip loading of A and Y register with the value) 2020-12-27 18:12:12 +01:00
dd82e550d5 adding rect and fillrect to gfx2 2020-12-27 17:34:25 +01:00
cdcda27d07 adding circle and disc to gfx2 2020-12-27 16:17:06 +01:00
ffffcdd50a project restructure, add experiment for httpCompilerService 2020-12-27 14:37:09 +01:00
d37d62574c project restructure 2020-12-27 07:21:39 +01:00
f2380457d6 update to new kotlin CLI parser library 2020-12-27 05:04:50 +01:00
efa42d5d96 compiler watch mode is a bit more robust now against crashes during compilation 2020-12-27 03:58:41 +01:00
e17c18b653 fix issues with memory() function, rewrite examples to use it 2020-12-27 03:35:56 +01:00
7607d3d64a check for unexecuted statements in blocks is now done for all blocks, not only main 2020-12-27 03:35:20 +01:00
d7d7147d43 added error message when not using returnvalue of a functioncall 2020-12-27 02:28:40 +01:00
b40e1eabb9 added memory() function for memory slab allocations 2020-12-27 02:28:30 +01:00
3b8e18004c fixed callgraph issue that allocated ALL variables in a (library) module even though some clearly weren't used at all. Variables declared in block level scope in a library are still all allocated / defined due to the nature of a library module with lists of definitions 2020-12-27 01:02:36 +01:00
4c03950c28 changed 'c64colors' module to 'palette' and added more general Cx16 palette manipulation routines in there. 2020-12-27 00:35:25 +01:00
170a0183f8 added 'inline' keyword to force inlining of trivial subroutines 2020-12-26 05:34:14 +01:00
c62ff16f8b added gfx2.text_charset() 2020-12-26 03:15:24 +01:00
ab495fe6e1 added gfx2.text() 2020-12-26 02:25:53 +01:00
c2a8dc23d0 R0-R15 register parameter optimization if loaded with byte instead of word 2020-12-25 22:30:40 +01:00
6734ae3c88 imageviewer now uses gfx2 for full-screen graphics. gfx2 promoted to built-in library on the cx16 target. 2020-12-25 17:57:46 +01:00
4c1c595f14 removed requirement of virtual regs R0-R15 to be at start of subroutine params 2020-12-25 15:43:48 +01:00
9002c67639 cleanup of cx16 regs lists 2020-12-25 14:00:07 +01:00
b91aabd3c0 max 16 subroutine params 2020-12-25 03:02:34 +01:00
3307f673f6 optimized cx16.vpoke etc. to be asmsubroutines instead 2020-12-24 07:12:59 +01:00
07b00bec61 fix problems with color cycling in iff viewer 2020-12-24 06:48:15 +01:00
e0d2b60d8b added diskio.f_read_exact() 2020-12-24 06:24:52 +01:00
45bfecee73 fix problems with color cycling in iff viewer 2020-12-24 05:46:57 +01:00
80e3a11268 fix faulty word[x]-- , fix invalid stz addressing modes 2020-12-24 04:08:52 +01:00
38a6c6a866 error message for too large repeat iteration count 2020-12-24 03:25:46 +01:00
8f224afed9 added color cycling support to iff viewer 2020-12-23 23:23:16 +01:00
48a4c46a6c optimized iff planar to chunky 2020-12-23 19:48:44 +01:00
7d08380c7f added cx16.vaddr() 2020-12-23 05:04:19 +01:00
b3b3cf3807 todo 2020-12-23 02:53:30 +01:00
f0f6150e18 fix problem with reuse of auto-indexer-variables that could result in wrong code for routines using multiple array indexings 2020-12-23 02:30:46 +01:00
dc600cc3ed fix crash when printing Ast for asmsubroutine with multiple return values 2020-12-23 02:03:27 +01:00
ae648b8a0a subroutines can now be defined even within regular code and will not disrupt the generated code anymore (they are moved to the end of their scope by the compiler) 2020-12-23 01:55:47 +01:00
583af3bd4f additional vpoke operations to do or,and,xor in one go without the need for a separate vpeek 2020-12-23 01:02:43 +01:00
d65cfbf093 fixed math.mul_word_40 that was actually doing *80... 2020-12-23 00:54:11 +01:00
118aed2e31 optimized code for 65c02 when setting constant 0 value 2020-12-22 17:59:47 +01:00
85abf4d123 update docs 2020-12-22 16:44:05 +01:00
44b8291540 update docs 2020-12-22 13:29:16 +01:00
d6444bba66 don't remove 'double' assignments that are actually doing something like calling a function 2020-12-22 12:52:55 +01:00
5a2f8fdfe1 asm-subroutines that ONLY return a value in the Carry or Overflow status register can now be used in an assignment to store that value. 2020-12-22 12:44:03 +01:00
bba4f84503 added target() function 2020-12-22 06:13:14 +01:00
684e081399 optimized register save/restore on Cx16 cpu target 2020-12-22 05:59:01 +01:00
96c700ee46 only save A's value if needed for a return value 2020-12-22 05:43:02 +01:00
5f15794c3b new compiled dirlist example 2020-12-22 04:58:33 +01:00
a40b3134f4 fix clobbering of A when restoring X or Y from stack 2020-12-22 04:52:46 +01:00
c70b4daf87 cleanup obsolete routine 2020-12-22 03:40:44 +01:00
928611eb20 Got rid of problematic attempts to save status register after function calls. If you really need it (for instance for if_XX instructions) it's probably better to use a short asmsub wrapper.
For function calls, register saves go via stack (to allow nested saves) for simpler cases, registers are saved in a local variable.
Fixed too agressive removal of sta-lda sequence if the lda is followed by a branching instruction.
Insert missing cmp #0 after functioncall if the value of the A register is needed in a comparison expression (could otherwise test wrong status flag)
2020-12-22 03:35:00 +01:00
f1d55c688a cx16 registers should come first in subroutine arg list 2020-12-22 00:59:07 +01:00
d22df22f7d fix examples for cx16 register syntax 2020-12-21 23:45:26 +01:00
061e1be0a4 removed ROM-float optimizations, too troublesome. Fixed LOG2 not being defined on Cx16 as well. 2020-12-21 23:22:02 +01:00
950bc4b937 cx16 virtual registers R0-R15 also available on C64 target (although in a different location in memory) 2020-12-21 21:04:29 +01:00
dcb81e6bea adding CommanderX16 virtual registers language support, rewrite cx16 examples 2020-12-21 20:38:00 +01:00
daaa83ee7d improved parsing of cpu registers (no more crash when invalid register) also adding CommanderX16 virtual registers language support 2020-12-21 19:19:53 +01:00
b7c1450121 upgrade to Antlr 4.9 2020-12-21 19:19:04 +01:00
787f52d1f8 doc 2020-12-21 18:28:10 +01:00
50213f146a undefined symbol errors are no longer reported one at a time but all at once 2020-12-21 13:03:56 +01:00
7f2aea60c9 addition 2020-12-19 03:36:52 +01:00
168621f7c2 addition 2020-12-19 03:27:08 +01:00
8b630798d8 documented the subroutine calling convention 2020-12-19 03:18:40 +01:00
52e8a44517 version 5.4 2020-12-15 22:59:02 +01:00
59f33658ad removed the rom path argument for launching the x16emu which made it fail on a non-Linux system 2020-12-15 22:51:10 +01:00
e0315bffdc decided not to change mkword() again, added note to docs about argument order 2020-12-15 22:25:06 +01:00
cd28d0c0e0 tweak 2020-12-14 21:57:16 +01:00
0baa2c8b23 fix oversight in binexpr operand swap that could result in suboptimal code 2020-12-14 21:37:40 +01:00
4977d1fbd5 bit shift expressions are "expanded" to the target value's datatype, now also for subroutine arguments.
implemented word bit shifts by variable number of bits.
2020-12-14 20:44:48 +01:00
3b7a92f1b4 adding strcopy() 2020-12-14 17:26:17 +01:00
f6920172dd image viewer tweaks 2020-12-14 15:36:15 +01:00
93bfc8f5f4 rename 2020-12-14 14:30:55 +01:00
39b7655264 imageviewer is now a single program 2020-12-14 14:30:18 +01:00
8b75ceb412 diskio.list_files now has a bigger buffer to store more filenames (around 30-40 max) 2020-12-14 14:29:42 +01:00
c39fc4010d textio.clear_screen() now uses kernal routine to clear the text screen, this also resets the cursor to top left. 2020-12-14 14:28:53 +01:00
8df778a515 fixed crash when importing modules from the same directory as the main program 2020-12-14 13:14:12 +01:00
5134ea76bf added bmp viewer 2020-12-14 02:12:26 +01:00
3ba37df29d added iff viewer 2020-12-13 19:42:30 +01:00
e221d674d9 pcxviewer done 2020-12-13 01:32:03 +01:00
251f947293 fixed parameter signature for FB_set_8_pixels_opaque() (docs are wrong) 2020-12-12 03:32:01 +01:00
41e1e1cbb0 adding pcxviewer 2020-12-12 02:40:54 +01:00
da1bc351d2 koalaviewer auto disk detect 2020-12-11 23:32:47 +01:00
43c0afdea0 fixed strlen() to work on arguments other than just a variable 2020-12-11 23:32:29 +01:00
add5bfa2ec koalaviewer scans directory for *.koa 2020-12-11 23:00:58 +01:00
34babfb5de added diskio.list_files(). ci-viewer now loads all *.ci files it finds. 2020-12-11 22:36:14 +01:00
4f6c45c86c incremental file loading 2020-12-11 21:05:03 +01:00
e6220a464c using progend() to maximize amount of mem available to load image 2020-12-10 23:52:30 +01:00
8dcd49934a added progend() builtin function 2020-12-10 23:33:45 +01:00
bedc3bdb56 allow bit shifting to be as large as the target variable's datatype 2020-12-10 22:49:27 +01:00
83ceb0fde9 optimize various simple cases for '**' (pow) like 2**x => bitshift 2020-12-10 22:37:12 +01:00
1d299c56e0 fix float '**' (pow) on cx16 2020-12-10 22:19:07 +01:00
0d735c2ccc workaround for FB_set_pixels bug 2020-12-10 21:51:32 +01:00
4094f89d4a not a bug 2020-12-10 03:22:43 +01:00
cf1e8b194a fix compiler crash for expressions of the form x = x and y (the logical booleans, not the bitwise) 2020-12-10 03:12:32 +01:00
74e5644f55 working on CI viewer 2020-12-10 03:00:37 +01:00
b5dc5fc615 added iterative file loading to diskio 2020-12-10 00:58:59 +01:00
7a7270d769 adding CI (CommanderX16 Image) file viewer 2020-12-10 00:03:47 +01:00
7549ddcd2b added TODOs for missing assignments 2020-12-10 00:03:20 +01:00
08f0303178 diskio status() now returns the status string instead of printing it 2020-12-10 00:02:21 +01:00
0d7a291b81 regenerated example disk , version 5.3 2020-12-08 23:15:31 +01:00
2265ae9600 optimized setting word values into array if index is fixed number 2020-12-08 22:54:20 +01:00
cba502e87a fixed crash when trying to assign a string literal to an array element in a string-array 2020-12-08 22:27:42 +01:00
ac94236614 fixed compiler crash when declaring a str(pointer) array without initializer 2020-12-08 22:19:11 +01:00
ddf1be2a13 status condition couldn't properly be tested because restoring the X register clobbers the status flag 2020-12-08 22:15:07 +01:00
b7694686c2 optimized code for branches containing just a goto or break statement 2020-12-08 22:00:52 +01:00
63332c0530 fix wrong branch instructions for some if_xxx 2020-12-08 21:29:40 +01:00
8a504f8eee fixed compiler crash: when passing the name of a subroutine instead of an array or string to an UWORD parameter
now allows taking the address of a subroutine &routine
2020-12-08 21:17:31 +01:00
106fc5daa4 tweak 2020-12-08 03:39:45 +01:00
7accb73993 iterative file listing instead 2020-12-08 03:34:45 +01:00
e9aa6a0956 TODOs 2020-12-08 02:20:24 +01:00
df20467e03 completed diskio file lister 2020-12-08 02:16:41 +01:00
ecbd9d739e completed diskio file lister 2020-12-08 01:34:08 +01:00
8af17c295a fixed diskio directory block sizes 2020-12-08 01:02:38 +01:00
329b28cad1 making diskio.listfiles 2020-12-07 23:49:34 +01:00
452c29574d added optimized mul 320 routine 2020-12-07 22:55:16 +01:00
5bedc1b333 remove test file 2020-12-06 18:40:47 +01:00
0bf6d2f72c tweak 2020-12-06 18:38:27 +01:00
c09b8af491 optimized koalaviewer to plot 8 pixels at once in the loop 2020-12-06 18:25:01 +01:00
260bcd3a55 added syntax error for non-constant array size declaration 2020-12-06 17:02:56 +01:00
6b5211ad12 tweak word shift unroll 2020-12-06 08:36:19 +01:00
a92ec14989 use 'stz' more often on 65c02 cpu (cx16) 2020-12-06 08:30:13 +01:00
b3348eb22b formatting 2020-12-06 07:52:58 +01:00
bec5a261e5 optimizing koalaviewer 2020-12-06 07:47:54 +01:00
4b53641e1d optimized text screen clear/fill and scrolling on c64 2020-12-06 01:16:31 +01:00
00071d53d5 optimized disc (filled circle) drawing on c64, fixed off by 1 disc width in cx16 version 2020-12-06 00:33:32 +01:00
6902834568 remove dummy argument for txt.scroll_XXXX() functions on cx16 2020-12-06 00:19:47 +01:00
fa2d87f3dd optimized disc (filled circle) drawing on cx16 2020-12-06 00:01:19 +01:00
44019d1a61 strings and arrays are no longer directly assignable to an UWORD, you need an explicit & (address-of) now 2020-12-03 18:39:32 +01:00
6f74fb49bd added c64colors module. added vpeek/vpoke to cx16 syslib. koalaviewer example now uses better c64 color palette. 2020-12-03 18:14:49 +01:00
a303b39cf0 added C64 'koala' image viewer example for Cx16 2020-12-03 16:02:51 +01:00
3e63a29c59 diskio now properly closes files after a load or save 2020-12-03 16:01:58 +01:00
261c0fc9b6 started adding syntax highlighting files 2020-12-02 20:48:50 +01:00
895b30f7e5 version 5.2 2020-12-01 22:49:08 +01:00
b985604e22 slight tweak to word bitshift for large shift values 2020-12-01 22:48:02 +01:00
f7953e4ef3 fix float comparison error that creeped in with no longer using the stack for that 2020-12-01 22:19:03 +01:00
63483d1f0e warnings, errors and todos 2020-12-01 03:24:06 +01:00
8b981f03bf optimized reg_lesseq_w (word <= word) to avoid using extra zp word, by swapping operands 2020-12-01 02:09:48 +01:00
d0d0910bf2 corrected greatereq_w (word >= word) 2020-12-01 01:57:12 +01:00
57ac820767 readme 2020-11-30 22:42:51 +01:00
b8bda867b6 optimized reg_lesseq_w (word <= word) 2020-11-30 02:26:00 +01:00
05d3a2450c optimized reg_less_w (word < word) 2020-11-30 01:53:44 +01:00
d40788adfa optimized in-place array element modification to use simpler assignment asm code 2020-11-28 00:44:38 +01:00
83fbf86b1c no longer generate double assignment to the indexer var for in-place modifying array variable 2020-11-27 23:46:01 +01:00
e876008427 tiny tweak of typecasting str to uword 2020-11-26 19:21:07 +01:00
2b43353eb4 readme 2020-11-26 02:04:01 +01:00
a74403c347 float typecasts optimization 2020-11-26 01:52:48 +01:00
2f4c6c8697 float typecasts optimization 2020-11-26 01:39:27 +01:00
238d8197f5 byte/word typecasts optimized even further to just use cpu registers (and fixed sign extending AY) 2020-11-26 01:33:45 +01:00
53a600d87b fix typecasting of signed byte to signed word in a variable 2020-11-25 22:33:41 +01:00
2a0ffaf45d started to optimize typecasts to use translateExpression() less 2020-11-25 00:17:42 +01:00
936b046ed9 optimize word [operator] byte, without translateExpression() 2020-11-24 23:41:10 +01:00
378dcfe351 fix computation error of word - byte 2020-11-24 22:23:16 +01:00
0a330b9288 warmings 2020-11-24 22:21:54 +01:00
a88b40d6c1 fix stack corruption with bitshifts 2020-11-24 21:58:14 +01:00
09f25ffbd9 optimized in-place memory var modification, not using translateExpression() 2020-11-24 21:41:44 +01:00
ab1232d742 optimized in-place float var modification, not using translateExpression() 2020-11-24 01:09:24 +01:00
a7f56fe0fc remaining float comparisons with expression now without translateExpression() 2020-11-24 00:35:30 +01:00
58a9452c36 fixed the YSCROLL graphics mode on the C64 (mistake in 5.1) 2020-11-23 23:05:51 +01:00
6d8c4f403f updated Kotlin version to 1.4.20, updated targeted JDK version to 11 (LTS) 2020-11-23 22:28:24 +01:00
88b80fed90 returning float values now via fac1 instead of stack 2020-11-23 22:14:45 +01:00
acdbd0c391 todos for next version 2020-11-22 19:18:57 +01:00
d9a8cfed8c updated the compiled examples disk 2020-11-22 18:45:40 +01:00
122796fbba version 5.1 2020-11-22 18:36:04 +01:00
510ca042c9 stack tested for most example programs 2020-11-22 18:35:43 +01:00
125f6205f2 optimizing assigning an array value to a var 2020-11-22 17:44:55 +01:00
8136f3df5c float-const comparison optimizations 2020-11-22 16:54:02 +01:00
38d06a7e94 optimized float var comparison without translateExpression() 2020-11-22 15:05:45 +01:00
49db10539a optimized float var equality comparison without translateExpression() 2020-11-22 14:33:03 +01:00
8efe4c6267 Fixed compiler watch to work with multiple compilation modules 2020-11-22 13:11:33 +01:00
04d8bb8fbf Fixed compiler watch flag crashing when not used on a subdirectory. Fixes #20 2020-11-22 12:07:14 +01:00
08aa13c90c rnd() functions marked as having (internal) side effect 2020-11-22 02:09:32 +01:00
d1febc0208 all in-place byte assignments now without translateExpression() 2020-11-22 01:38:53 +01:00
5980e58ac6 word comparison jumps now without translateExpression() 2020-11-22 01:15:05 +01:00
e1dc283d4b byte comparison jumps now without translateExpression() 2020-11-21 23:31:26 +01:00
8be234973c rollback failed optimization of memory expressions (code size got too large) 2020-11-21 19:09:02 +01:00
7def8ff2cd beginning to optimize comparisons more 2020-11-21 18:44:17 +01:00
340b1c2e42 added balls demo/benchmark 2020-11-21 18:03:57 +01:00
7e0f7ba438 todos 2020-11-20 23:46:14 +01:00
fefd9b52a8 fix for loop with signed byte loopvar over non-const 2020-11-20 22:54:24 +01:00
afd155ac4f optimize for loops over non const range, without translateExpression() 2020-11-20 22:44:16 +01:00
ee724eb4f1 float variable casts without translateExpression() 2020-11-19 22:58:38 +01:00
2f1f20ea11 rename 2020-11-19 00:28:49 +01:00
063bcf17d8 various inplace modification for word vars now without translateExpression() 2020-11-19 00:08:10 +01:00
72509eef44 inplace modification for memory now without translateExpression() 2020-11-18 23:23:06 +01:00
2da28864e9 inplace not and invert for memory now without translateExpression() 2020-11-18 23:13:07 +01:00
4278f64682 fixed invalid value push for memreads with expression 2020-11-18 22:45:04 +01:00
59ae3c3fcd << and >> for byte values slightly optimized, no longer use translateExpression(). preparing for more operator optimizations. 2020-11-18 01:27:02 +01:00
7fa21fbdff @(...) in an expression is now more efficient, without translateExpression() 2020-11-18 00:58:04 +01:00
e95af7498e comparing function call result to 0 now more efficient, without translateExpression() 2020-11-18 00:05:48 +01:00
79c75adac1 repeat and when without translateExpression() 2020-11-17 23:52:13 +01:00
d212f69d89 ++/-- and @Pc without translateExpression() 2020-11-17 23:40:42 +01:00
edf5e69d39 optimized swap() 2020-11-15 18:04:54 +01:00
574eb0d174 refactoring asmassignment code blocks into utility functions 2020-11-15 17:44:47 +01:00
8bd4914e2f fix stack error for float casts 2020-11-15 17:34:27 +01:00
5ebaaff64b refactoring asmassignment code blocks into utility functions 2020-11-15 15:07:55 +01:00
5c9e0c9f51 emit extra nop for breakpoints so vice label list works again (requires 64tass 1.55.2257 or newer!) 2020-11-15 14:31:06 +01:00
8132edbb08 updated some compiled example 2020-11-10 22:51:01 +01:00
d29ce78c86 todos and version 2020-11-10 22:44:48 +01:00
94bc9d7a69 string compare in expression no longer via stack args 2020-11-10 21:48:28 +01:00
e8faec0932 re-introduced more aggressive binexpr splitting optimization 2020-11-10 21:17:33 +01:00
69ca4fe304 cleanup 2020-11-10 21:02:12 +01:00
cd99fe46fd finished call convention change for builtin functions now no longer via stack 2020-11-10 00:43:45 +01:00
4825b4dc68 fix passing address of pass-by-reference assignment to a UWORD 2020-11-10 00:35:24 +01:00
8d0607ef58 fix missing float casts 2020-11-09 23:57:50 +01:00
225295a7d8 fix float casts 2020-11-09 01:18:22 +01:00
4cd74daf53 float eval result var added, but some examples are broken 2020-11-08 18:54:02 +01:00
6eb9118197 example 2020-11-07 01:08:56 +01:00
d0bd2f522c rol and ror 2020-11-07 00:56:54 +01:00
661c757236 fix string compare in expressions 2020-11-06 22:59:56 +01:00
aaa20093ef cleaning up and correcting cc for builtin functions 2020-11-06 00:56:26 +01:00
1eecdd6fa3 fix error when taking address of struct var 2020-11-05 02:39:04 +01:00
800b5b2a43 cleaning up and correcting cc for builtin functions 2020-11-05 02:29:33 +01:00
9d17421c66 implemented the arithmetic functions with new cc. fixed sgn(). 2020-11-04 02:27:29 +01:00
0edd50e956 implemented cc for abs() 2020-11-03 23:01:23 +01:00
288d4f08b3 implemented cc for integer sin and cos variants 2020-11-03 22:42:59 +01:00
526e4b8bdc fix faulty binexpr splitting 2020-11-03 21:31:08 +01:00
e0c5ccc16b begun with converting builtin functions to new call convention 2020-11-02 23:00:20 +01:00
ebc2c614d7 use non-stack call conv for builtin functions 2020-11-02 19:59:27 +01:00
29f5a85158 callconv 2020-11-01 19:25:23 +01:00
8af2380a47 pair 2020-11-01 18:00:20 +01:00
431f2a2088 optimized memset and memcopy on CX16, memcopy can deal with any size now 2020-11-01 08:00:32 +01:00
e05ea887f6 implement proper returning of float values via FAC1 2020-11-01 06:27:17 +01:00
95c0425151 improved sqrt16 2020-11-01 05:45:49 +01:00
47cbc7b1f9 added a custom-charset example for the c64 2020-10-31 02:26:59 +01:00
e7b75d591c assigning float results from functions (from FAC1) 2020-10-31 01:22:19 +01:00
99f7d469f4 assigning string result from subroutine 2020-10-30 22:22:06 +01:00
8a6ef17fbf option 2020-10-30 21:51:15 +01:00
5f337a0bd9 fix typecheck of multiple returnvalues 2020-10-30 21:45:37 +01:00
87862f772a better handling of inferred type errors 2020-10-30 21:24:49 +01:00
3ab641aa21 removed @stack in subroutine args and returnvalues, can only use variables or registers now 2020-10-30 15:02:42 +01:00
3efa8da8e0 made versions of various builtin funcs returning value in registers 2020-10-30 14:35:20 +01:00
3e28ed4fe4 mader versions of abs() and sgn() returning value in register 2020-10-28 22:56:13 +01:00
44949460ed change for subroutine return values via registers instead of stack 2020-10-28 00:29:34 +01:00
83cc19ad6f preparing for subroutine return values via registers instead of stack 2020-10-23 20:56:10 +02:00
66bb98c479 fixed bugs in code assigning values from eval stack 2020-10-23 03:45:09 +02:00
ff3f985658 refactoring 2020-10-22 23:41:16 +02:00
248 changed files with 28904 additions and 8385 deletions

2
.gitignore vendored
View File

@ -29,3 +29,5 @@ parsetab.py
.gradle .gradle
/prog8compiler.jar /prog8compiler.jar
sd*.img

View File

@ -3,14 +3,11 @@
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true"> <inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages> <Languages>
<language minSize="100" isEnabled="false" name="JavaScript" />
<language isEnabled="false" name="Groovy" />
<language isEnabled="false" name="Style Sheets" />
<language minSize="70" name="Kotlin" /> <language minSize="70" name="Kotlin" />
<language isEnabled="false" name="TypeScript" /> <language isEnabled="false" name="Groovy" />
<language isEnabled="false" name="ActionScript" />
</Languages> </Languages>
</inspection_tool> </inspection_tool>
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true"> <inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" /> <option name="processCode" value="false" />
<option name="processLiterals" value="true" /> <option name="processLiterals" value="true" />

2
.idea/kotlinc.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Kotlin2JvmCompilerArguments"> <component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="1.8" /> <option name="jvmTarget" value="11" />
</component> </component>
</project> </project>

View File

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

View File

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

View File

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

View File

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

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

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="dbus-java-3.2.4">
<CLASSES>
<root url="jar://$PROJECT_DIR$/dbusCompilerService/lib/dbus-java-3.2.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/libraries/javax_json_api_1_1_4.xml generated Normal file
View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="javax.json-api-1.1.4">
<CLASSES>
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-api-1.1.4.jar!/" />
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/javax.json-1.1.4.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

View File

@ -1,7 +1,7 @@
<component name="libraryTable"> <component name="libraryTable">
<library name="kotlinx-cli-jvm-0.1.0-dev-5"> <library name="kotlinx-cli-jvm">
<CLASSES> <CLASSES>
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" /> <root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.3.1.jar!/" />
</CLASSES> </CLASSES>
<JAVADOC /> <JAVADOC />
<SOURCES /> <SOURCES />

10
.idea/libraries/slf4j_api_1_7_30.xml generated Normal file
View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="slf4j-api-1.7.30">
<CLASSES>
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-api-1.7.30.jar!/" />
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/slf4j-simple-1.7.30.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

12
.idea/libraries/takes_http.xml generated Normal file
View File

@ -0,0 +1,12 @@
<component name="libraryTable">
<library name="takes-http">
<CLASSES>
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/cactoos-0.42.jar!/" />
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-lang3-3.7.jar!/" />
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/commons-text-1.4.jar!/" />
<root url="jar://$PROJECT_DIR$/httpCompilerService/lib/takes-1.19.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

2
.idea/misc.xml generated
View File

@ -16,7 +16,7 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<output url="file://$PROJECT_DIR$/out" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>
</project> </project>

3
.idea/modules.xml generated
View File

@ -3,8 +3,11 @@
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.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$/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$/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$/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$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
</modules> </modules>
</component> </component>

View File

@ -20,37 +20,42 @@ Full documentation (syntax reference, how to use the language and the compiler,
https://prog8.readthedocs.io/ https://prog8.readthedocs.io/
What use Prog8 provide? What does Prog8 provide?
----------------------- ------------------------
- reduction of source code length over raw assembly - reduction of source code length over raw assembly
- modularity, symbol scoping, subroutines - modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings) - various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string and array variables and string sharing - automatic variable allocations, automatic string and array variables and string sharing
- subroutines with an input- and output parameter signature - subroutines with input parameters and result values
- constant folding in expressions - high-level program optimizations
- small program boilerplate/compilersupport overhead
- sane variable initialization, programs can be restarted again just fine after exiting to basic
- conditional branches - conditional branches
- floating point operations (requires the C64 Basic ROM routines for this)
- '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
- structs to group together sets of variables and manipulate them at once - structs to group together sets of variables and manipulate them at once
- floating point operations (requires the C64 Basic ROM routines for this) - many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses - various powerful built-in libraries to do I/O, number conversions, graphics and more
- various code optimizations (code structure, logical and numerical expressions, unused code removal...) - convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- fast execution speed due to compilation to native assembly code
- variables are allocated statically
- inline assembly allows you to have full control when every cycle or byte matters - inline assembly allows you to have full control when every cycle or byte matters
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse`` - supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, and provides them also on the C64.
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
*Rapid edit-compile-run-debug cycle:* *Rapid edit-compile-run-debug cycle:*
- use a modern PC to do the work on - use a modern PC to do the work on, use nice editors and enjoy quick compilation times
- very quick compilation times
- can automatically run the program in the Vice emulator after succesful compilation - can automatically run the program in the Vice emulator after succesful compilation
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them - breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly - source code labels automatically loaded in Vice emulator so it can show them in disassembly
*Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!): *Two supported compiler targets* (contributions to improve these or to add support for other machines are welcome!):
- "c64": Commodore-64 (6510 CPU = almost a 6502) premium support. - "c64": Commodore-64 (6510 CPU = almost a 6502)
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) experimental support. - "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)! - 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)!
@ -61,7 +66,7 @@ Additional required tools
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project. A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
For other platforms it is very easy to compile it yourself (make ; make install). For other platforms it is very easy to compile it yourself (make ; make install).
A **Java runtime (jre or jdk), version 8 or newer** is required to run a prepackaged version of the compiler. A **Java runtime (jre or jdk), version 11 or newer** is required to run a prepackaged version of the compiler.
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance, If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
IntelliJ IDEA with the Kotlin plugin). IntelliJ IDEA with the Kotlin plugin).
@ -84,9 +89,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
ubyte candidate_prime = 2 ; is increased in the loop ubyte candidate_prime = 2 ; is increased in the loop
sub start() { sub start() {
; clear the sieve, to reset starting situation on subsequent runs sys.memset(sieve, 256, false) ; clear the sieve
memset(sieve, 256, false)
; calculate primes
txt.print("prime numbers up to 255:\n\n") txt.print("prime numbers up to 255:\n\n")
ubyte amount=0 ubyte amount=0
repeat { repeat {
@ -97,17 +100,17 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
txt.print(", ") txt.print(", ")
amount++ amount++
} }
txt.chrout('\n') txt.nl()
txt.print("number of primes (expected 54): ") txt.print("number of primes (expected 54): ")
txt.print_ub(amount) txt.print_ub(amount)
txt.chrout('\n') txt.nl()
} }
sub find_next_prime() -> ubyte { sub find_next_prime() -> ubyte {
while sieve[candidate_prime] { while sieve[candidate_prime] {
candidate_prime++ candidate_prime++
if candidate_prime==0 if candidate_prime==0
return 0 ; we wrapped; no more primes available in the sieve return 0 ; we wrapped; no more primes
} }
; found next one, mark the multiples and return it. ; found next one, mark the multiples and return it.
@ -124,6 +127,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
when compiled an ran on a C-64 you'll get: when compiled an ran on a C-64 you'll get:
![c64 screen](docs/source/_static/primes_example.png) ![c64 screen](docs/source/_static/primes_example.png)
@ -140,7 +144,8 @@ If you want to play a video game, a fully working Tetris clone is included in th
![tehtriz_screen](docs/source/_static/tehtriz.png) ![tehtriz_screen](docs/source/_static/tehtriz.png)
The CommanderX16 compiler target is quite capable already too, here's a well known space ship There are a couple of examples specially made for the CommanderX16 compiler target.
animated in 3D with hidden line removal, in the CommanderX16 emulator: For instance here's a well known space ship animated in 3D with hidden line removal,
in the CommanderX16 emulator:
![cobra3d](docs/source/_static/cobra3d.png) ![cobra3d](docs/source/_static/cobra3d.png)

View File

@ -1,41 +1,29 @@
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.10"
}
}
plugins { plugins {
// id "org.jetbrains.kotlin.jvm" version "1.4.10"
id 'application'
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.2.0'
id 'java' id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm" version "1.4.30"
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '6.1.0'
} }
apply plugin: "kotlin" targetCompatibility = 11
apply plugin: "java" sourceCompatibility = 11
targetCompatibility = 1.8
sourceCompatibility = 1.8
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
jcenter() maven { url "https://kotlin.bintray.com/kotlinx" }
maven { url "https://dl.bintray.com/orangy/maven/" }
} }
def prog8version = rootProject.file('compiler/res/version.txt').text.trim() def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies { dependencies {
implementation project(':parser') implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect" // implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.antlr:antlr4-runtime:4.8' implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5' // implementation 'net.razorvine:ksim65:1.8'
// implementation 'net.razorvine:ksim65:1.6' // implementation "com.github.hypfvieh:dbus-java:3.2.4"
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
implementation project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5" testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
@ -45,7 +33,8 @@ dependencies {
compileKotlin { compileKotlin {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "11"
useIR = true
// verbose = true // verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference" // freeCompilerArgs += "-XXLanguage:+NewInference"
} }
@ -53,7 +42,8 @@ compileKotlin {
compileTestKotlin { compileTestKotlin {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "11"
useIR = true
} }
} }
@ -76,7 +66,8 @@ sourceSets {
startScripts.enabled = true startScripts.enabled = true
application { application {
mainClassName = 'prog8.CompilerMainKt' mainClass = 'prog8.CompilerMainKt'
mainClassName = 'prog8.CompilerMainKt' // deprecated
applicationName = 'p8compile' applicationName = 'p8compile'
} }
@ -112,5 +103,5 @@ dokka {
} }
task wrapper(type: Wrapper) { task wrapper(type: Wrapper) {
gradleVersion = '6.1.1' gradleVersion = '6.7'
} }

View File

@ -11,9 +11,8 @@
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" /> <orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" /> <orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" /> <orderEntry type="library" name="unittest-libs" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" /> <orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
<orderEntry type="library" name="antlr-runtime-4.8" level="project" /> <orderEntry type="module" module-name="compilerAst" />
</component> </component>
</module> </module>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,20 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
tab_width = 8
trim_trailing_whitespace = true
ij_smart_tabs = true
[*.p8]
indent_size = 4
indent_style = space
[*.asm]
indent_size = 8
indent_style = tab

View File

@ -2,6 +2,8 @@
FL_ONE_const .byte 129 ; 1.0 FL_ONE_const .byte 129 ; 1.0
FL_ZERO_const .byte 0,0,0,0,0 ; 0.0 FL_ZERO_const .byte 0,0,0,0,0 ; 0.0
FL_LOG2_const .byte $80, $31, $72, $17, $f8 ; log(2)
floats_store_reg .byte 0 ; temp storage floats_store_reg .byte 0 ; temp storage
@ -55,13 +57,76 @@ w2float .proc
jmp ub2float._fac_to_mem jmp ub2float._fac_to_mem
.pend .pend
cast_from_uw .proc
; -- uword in A/Y into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
cast_from_w .proc
; -- word in A/Y into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr GIVAYFAY
jmp ub2float._fac_to_mem
.pend
cast_from_ub .proc
; -- ubyte in Y into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr FREADUY
jmp ub2float._fac_to_mem
.pend
cast_from_b .proc
; -- byte in A into float var at (P8ZP_SCRATCH_W2)
stx P8ZP_SCRATCH_REG
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
cast_as_uw_into_ya .proc ; also used for float 2 ub
; -- cast float at A/Y to uword into Y/A
jsr MOVFM
jmp cast_FAC1_as_uw_into_ya
.pend
cast_as_w_into_ay .proc ; also used for float 2 b
; -- cast float at A/Y to word into A/Y
jsr MOVFM
jmp cast_FAC1_as_w_into_ay
.pend
cast_FAC1_as_uw_into_ya .proc ; also used for float 2 ub
; -- cast fac1 to uword into Y/A
stx P8ZP_SCRATCH_REG
jsr GETADR ; into Y/A
ldx P8ZP_SCRATCH_REG
rts
.pend
cast_FAC1_as_w_into_ay .proc ; also used for float 2 b
; -- cast fac1 to word into A/Y
stx P8ZP_SCRATCH_REG
jsr AYINT
ldy $64
lda $65
ldx P8ZP_SCRATCH_REG
rts
.pend
stack_b2float .proc stack_b2float .proc
; -- b2float operating on the stack ; -- b2float operating on the stack
inx inx
lda P8ESTACK_LO,x lda P8ESTACK_LO,x
stx P8ZP_SCRATCH_REG stx P8ZP_SCRATCH_REG
jsr FREADSA jsr FREADSA
jmp push_fac1_as_result jmp push_fac1._internal
.pend .pend
stack_w2float .proc stack_w2float .proc
@ -71,7 +136,7 @@ stack_w2float .proc
lda P8ESTACK_HI,x lda P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG stx P8ZP_SCRATCH_REG
jsr GIVAYF jsr GIVAYF
jmp push_fac1_as_result jmp push_fac1._internal
.pend .pend
stack_ub2float .proc stack_ub2float .proc
@ -82,7 +147,7 @@ stack_ub2float .proc
tay tay
lda #0 lda #0
jsr GIVAYF jsr GIVAYF
jmp push_fac1_as_result jmp push_fac1._internal
.pend .pend
stack_uw2float .proc stack_uw2float .proc
@ -92,7 +157,7 @@ stack_uw2float .proc
ldy P8ESTACK_HI,x ldy P8ESTACK_HI,x
stx P8ZP_SCRATCH_REG stx P8ZP_SCRATCH_REG
jsr GIVUAYFAY jsr GIVUAYFAY
jmp push_fac1_as_result jmp push_fac1._internal
.pend .pend
stack_float2w .proc ; also used for float2b stack_float2w .proc ; also used for float2b
@ -146,22 +211,6 @@ push_float .proc
rts rts
.pend .pend
func_rndf .proc
; -- put a random floating point value on the stack
stx P8ZP_SCRATCH_REG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx #<_rndf_rnum5
ldy #>_rndf_rnum5
jsr MOVMF ; fac1 to mem X/Y
ldx P8ZP_SCRATCH_REG
lda #<_rndf_rnum5
ldy #>_rndf_rnum5
jmp push_float
_rndf_rnum5 .byte 0,0,0,0,0
.pend
pop_float .proc pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y ; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding) ; (frees 3 stack positions = 6 bytes of which 1 is padding)
@ -198,16 +247,6 @@ pop_float_fac1 .proc
jmp MOVFM jmp MOVFM
.pend .pend
pop_float_fac2 .proc
; -- pops float from stack into FAC2
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp CONUPK
.pend
copy_float .proc copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1, ; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y. ; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
@ -273,9 +312,11 @@ pop_2_floats_f2_in_fac1 .proc
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
push_fac1_as_result .proc
; -- push the float in FAC1 onto the stack, and return from calculation push_fac1 .proc
ldx #<fmath_float1 ; -- push the float in FAC1 onto the stack
stx P8ZP_SCRATCH_REG
_internal ldx #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr MOVMF jsr MOVMF
lda #<fmath_float1 lda #<fmath_float1
@ -284,6 +325,7 @@ push_fac1_as_result .proc
jmp push_float jmp push_float
.pend .pend
pow_f .proc pow_f .proc
; -- push f1 ** f2 on stack ; -- push f1 ** f2 on stack
lda #<fmath_float2 lda #<fmath_float2
@ -299,8 +341,7 @@ pow_f .proc
lda #<fmath_float2 lda #<fmath_float2
ldy #>fmath_float2 ldy #>fmath_float2
jsr FPWR jsr FPWR
ldx P8ZP_SCRATCH_REG jmp push_fac1._internal
jmp push_fac1_as_result
.pend .pend
div_f .proc div_f .proc
@ -310,7 +351,7 @@ div_f .proc
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr FDIV jsr FDIV
jmp push_fac1_as_result jmp push_fac1._internal
.pend .pend
add_f .proc add_f .proc
@ -320,7 +361,7 @@ add_f .proc
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr FADD jsr FADD
jmp push_fac1_as_result jmp push_fac1._internal
.pend .pend
sub_f .proc sub_f .proc
@ -330,7 +371,7 @@ sub_f .proc
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr FSUB jsr FSUB
jmp push_fac1_as_result jmp push_fac1._internal
.pend .pend
mul_f .proc mul_f .proc
@ -340,7 +381,7 @@ mul_f .proc
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr FMULT jsr FMULT
jmp push_fac1_as_result jmp push_fac1._internal
.pend .pend
neg_f .proc neg_f .proc
@ -351,11 +392,96 @@ neg_f .proc
rts rts
.pend .pend
abs_f .proc var_fac1_less_f .proc
; -- strip the sign bit on the stack ; -- is the float in FAC1 < the variable AY?
lda P8ESTACK_HI+3,x stx P8ZP_SCRATCH_REG
and #$7f jsr FCOMP
sta P8ESTACK_HI+3,x ldx P8ZP_SCRATCH_REG
cmp #255
beq +
lda #0
rts
+ lda #1
rts
.pend
var_fac1_lesseq_f .proc
; -- is the float in FAC1 <= the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #0
beq +
cmp #255
beq +
lda #0
rts
+ lda #1
rts
.pend
var_fac1_greater_f .proc
; -- is the float in FAC1 > the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #1
beq +
lda #0
+ rts
.pend
var_fac1_greatereq_f .proc
; -- is the float in FAC1 >= the variable AY?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #0
beq +
cmp #1
beq +
lda #0
rts
+ lda #1
rts
.pend
var_fac1_notequal_f .proc
; -- are the floats numbers in FAC1 and the variable AY *not* identical?
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
and #1
rts
.pend
vars_equal_f .proc
; -- are the mflpt5 numbers in P8ZP_SCRATCH_W1 and AY identical?
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
iny
lda (P8ZP_SCRATCH_W1),y
cmp (P8ZP_SCRATCH_W2),y
bne _false
lda #1
rts
_false lda #0
rts rts
.pend .pend
@ -396,6 +522,40 @@ notequal_f .proc
rts rts
.pend .pend
vars_less_f .proc
; -- is float in AY < float in P8ZP_SCRATCH_W2 ?
jsr MOVFM
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
bne +
lda #1
rts
+ lda #0
rts
.pend
vars_lesseq_f .proc
; -- is float in AY <= float in P8ZP_SCRATCH_W2 ?
jsr MOVFM
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
stx P8ZP_SCRATCH_REG
jsr FCOMP
ldx P8ZP_SCRATCH_REG
cmp #255
bne +
- lda #1
rts
+ cmp #0
beq -
lda #0
rts
.pend
less_f .proc less_f .proc
; -- is f1 < f2? ; -- is f1 < f2?
jsr compare_floats jsr compare_floats
@ -457,240 +617,22 @@ _return_true lda #1
bne _return_result bne _return_result
.pend .pend
func_sin .proc set_array_float_from_fac1 .proc
; -- push sin(f) back onto stack ; -- set the float in FAC1 in the array (index in A, array in P8ZP_SCRATCH_W1)
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr SIN
jmp push_fac1_as_result
.pend
func_cos .proc
; -- push cos(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr COS
jmp push_fac1_as_result
.pend
func_tan .proc
; -- push tan(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr TAN
jmp push_fac1_as_result
.pend
func_atan .proc
; -- push atan(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr ATN
jmp push_fac1_as_result
.pend
func_ln .proc
; -- push ln(f) back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr LOG
jmp push_fac1_as_result
.pend
func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr LOG
jsr MOVEF
lda #<c64.FL_LOG2
ldy #>c64.FL_LOG2
jsr MOVFM
jsr FDIVT
jmp push_fac1_as_result
.pend
func_sqrt .proc
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr SQR
jmp push_fac1_as_result
.pend
func_rad .proc
; -- convert degrees to radians (d * pi / 180)
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
lda #<_pi_div_180
ldy #>_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend
func_deg .proc
; -- convert radians to degrees (d * (1/ pi * 180))
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend
func_round .proc
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr FADDH
jsr INT
jmp push_fac1_as_result
.pend
func_floor .proc
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
jsr INT
jmp push_fac1_as_result
.pend
func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1
stx P8ZP_SCRATCH_REG
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1
ldy #>fmath_float1
jsr FCOMP
cmp #0
beq +
lda #<FL_ONE_const
ldy #>FL_ONE_const
jsr FADD
+ jmp push_fac1_as_result
.pend
func_any_f .proc
inx
lda P8ESTACK_LO,x ; array size
sta P8ZP_SCRATCH_B1 sta P8ZP_SCRATCH_B1
asl a asl a
asl a asl a
clc clc
adc P8ZP_SCRATCH_B1 ; times 5 because of float adc P8ZP_SCRATCH_B1
jmp prog8_lib.func_any_b._entry
.pend
func_all_f .proc
inx
jsr prog8_lib.peek_address
lda P8ESTACK_LO,x ; array size
sta P8ZP_SCRATCH_B1
asl a
asl a
clc
adc P8ZP_SCRATCH_B1 ; times 5 because of float
tay
dey
- lda (P8ZP_SCRATCH_W1),y
clc
dey
adc (P8ZP_SCRATCH_W1),y
dey
adc (P8ZP_SCRATCH_W1),y
dey
adc (P8ZP_SCRATCH_W1),y
dey
adc (P8ZP_SCRATCH_W1),y
dey
cmp #0
beq +
cpy #255
bne -
lda #1
sta P8ESTACK_LO+1,x
rts
+ sta P8ESTACK_LO+1,x
rts
.pend
func_max_f .proc
lda #255
sta _minmax_cmp+1
lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx floats_store_reg
- sty P8ZP_SCRATCH_REG
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1 ldy P8ZP_SCRATCH_W1+1
jsr FCOMP
_minmax_cmp cmp #255 ; modified
bne +
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr MOVFM
+ lda P8ZP_SCRATCH_W1
clc clc
adc #5 adc P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
bcc + bcc +
inc P8ZP_SCRATCH_W1+1 iny
+ ldy P8ZP_SCRATCH_REG + stx floats_store_reg
dey tax
cpy #255 jsr MOVMF
bne -
ldx floats_store_reg ldx floats_store_reg
stx P8ZP_SCRATCH_REG
jmp push_fac1_as_result
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend
func_min_f .proc
lda #1
sta func_max_f._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f._minmax_entry
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend
func_sum_f .proc
lda #<FL_ZERO_const
ldy #>FL_ZERO_const
jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx floats_store_reg
- sty P8ZP_SCRATCH_REG
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
jsr FADD
ldy P8ZP_SCRATCH_REG
dey
cpy #255
beq +
lda P8ZP_SCRATCH_W1
clc
adc #5
sta P8ZP_SCRATCH_W1
bcc -
inc P8ZP_SCRATCH_W1+1
bne -
+ ldx floats_store_reg
stx P8ZP_SCRATCH_REG
jmp push_fac1_as_result
.pend
sign_f .proc
jsr pop_float_fac1
jsr SIGN
sta P8ESTACK_LO,x
dex
rts rts
.pend .pend
@ -734,16 +676,3 @@ set_array_float .proc
.pend .pend
swap_floats .proc
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
ldy #4
- lda (P8ZP_SCRATCH_W1),y
pha
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
pla
sta (P8ZP_SCRATCH_W2),y
dey
bpl -
rts
.pend

View File

@ -19,23 +19,6 @@ floats {
; note: the fac1 and fac2 are working registers and take 6 bytes each, ; note: the fac1 and fac2 are working registers and take 6 bytes each,
; floats in memory (and rom) are stored in 5-byte MFLPT packed format. ; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
; constants in five-byte "mflpt" format in the BASIC ROM
&float FL_PIVAL = $aea8 ; 3.1415926...
&float FL_N32768 = $b1a5 ; -32768
&float FL_FONE = $b9bc ; 1
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
&float FL_SQRTWO = $b9db ; SQR(2)
&float FL_NEGHLF = $b9e0 ; -.5
&float FL_LOG2 = $b9e5 ; LOG(2)
&float FL_TENC = $baf9 ; 10
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
&float FL_FHALF = $bf11 ; .5
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
&float FL_PIHALF = $e2e0 ; PI / 2
&float FL_TWOPI = $e2e5 ; 2 * PI
&float FL_FR4 = $e2ea ; .25
; oddly enough, 0.0 isn't available in the kernel.
; note: fac1/2 might get clobbered even if not mentioned in the function's name. ; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1. ; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
@ -213,5 +196,6 @@ sub print_f (float value) {
} }
%asminclude "library:c64/floats.asm", "" %asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
} }

View File

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

View File

@ -2,7 +2,7 @@
%import textio %import textio
; bitmap pixel graphics module for the C64 ; bitmap pixel graphics module for the C64
; only black/white monchrome 320x200 for now ; only black/white monochrome 320x200 for now
; assumes bitmap screen memory is $2000-$3fff ; assumes bitmap screen memory is $2000-$3fff
graphics { graphics {
@ -12,29 +12,52 @@ graphics {
sub enable_bitmap_mode() { sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white. ; enable bitmap screen, erase it and set colors to black/white.
c64.SCROLY |= %00100000 c64.SCROLY = %00111011
c64.SCROLX = %00001000
c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff c64.VMCSB = (c64.VMCSB & %11110000) | %00001000 ; $2000-$3fff
clear_screen(1, 0) clear_screen(1, 0)
} }
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
c64.SCROLY = %00011011
c64.SCROLX = %00001000
c64.VMCSB = (c64.VMCSB & %11110000) | %00000100 ; $1000-$2fff
txt.fill_screen(' ', 1)
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) { sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
memset(BITMAP_ADDRESS, 320*200/8, 0) sys.memset(BITMAP_ADDRESS, 320*200/8, 0)
txt.fill_screen(pixelcolor << 4 | bgcolor, 0) txt.fill_screen(pixelcolor << 4 | bgcolor, 0)
} }
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) { sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; Bresenham algorithm. ; Bresenham algorithm.
; This code special cases various quadrant loops to allow simple ++ and -- operations. ; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; TODO rewrite this in optimized assembly ; TODO there are some slight errors at the first/last pixels in certain slopes...??
if y1>y2 { if y1>y2 {
; make sure dy is always positive to avoid 8 instead of just 4 special cases ; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2) swap(x1, x2)
swap(y1, y2) swap(y1, y2)
} }
word @zp d = 0
ubyte positive_ix = true
word @zp dx = x2-x1 as word word @zp dx = x2-x1 as word
word @zp dy = y2-y1 word @zp dy = y2-y1
if dx==0 {
vertical_line(x1, y1, abs(dy)+1 as ubyte)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, y1, abs(dx)+1 as uword)
return
}
; TODO rewrite the rest in optimized assembly
word @zp d = 0
ubyte positive_ix = true
if dx < 0 { if dx < 0 {
dx = -dx dx = -dx
positive_ix = false positive_ix = false
@ -99,78 +122,173 @@ graphics {
} }
} }
sub rect(uword x, ubyte y, uword width, ubyte height) {
if width==0 or height==0
return
horizontal_line(x, y, width)
if height==1
return
horizontal_line(x, y+height-1, width)
vertical_line(x, y+1, height-2)
if width==1
return
vertical_line(x+width-1, y+1, height-2)
}
sub fillrect(uword x, ubyte y, uword width, ubyte height) {
if width==0
return
repeat height {
horizontal_line(x, y, width)
y++
}
}
sub horizontal_line(uword x, ubyte y, uword length) {
if length<8 {
internal_plotx=x
repeat lsb(length) {
internal_plot(y)
internal_plotx++
}
return
}
ubyte separate_pixels = lsb(x) & 7
uword addr = get_y_lookup(y) + (x&$fff8)
if separate_pixels {
%asm {{
lda addr
sta P8ZP_SCRATCH_W1
lda addr+1
sta P8ZP_SCRATCH_W1+1
ldy separate_pixels
lda _filled_right,y
eor #255
ldy #0
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W1),y
}}
addr += 8
length += separate_pixels
length -= 8
}
if length {
%asm {{
lda length
and #7
sta separate_pixels
stx P8ZP_SCRATCH_REG
lsr length+1
ror length
lsr length+1
ror length
lsr length+1
ror length
lda addr
sta _modified+1
lda addr+1
sta _modified+2
lda length
ora length+1
beq _zero
ldy length
ldx #$ff
_modified stx $ffff ; modified
lda _modified+1
clc
adc #8
sta _modified+1
bcc +
inc _modified+2
+ dey
bne _modified
_zero ldx P8ZP_SCRATCH_REG
ldy separate_pixels
beq _zero2
lda _modified+1
sta P8ZP_SCRATCH_W1
lda _modified+2
sta P8ZP_SCRATCH_W1+1
lda _filled_right,y
ldy #0
ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W1),y
jmp _zero2
_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110
_zero2
}}
}
}
sub vertical_line(uword x, ubyte y, ubyte height) {
internal_plotx = x
repeat height {
internal_plot(y)
y++
}
}
sub circle(uword xcenter, ubyte ycenter, ubyte radius) { sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm ; Midpoint algorithm
if radius==0
return
ubyte @zp ploty ubyte @zp ploty
ubyte @zp xx = radius
ubyte @zp yy = 0 ubyte @zp yy = 0
byte @zp decisionOver2 = 1-xx as byte word @zp decisionOver2 = (1 as word)-radius
while xx>=yy { while radius>=yy {
internal_plotx = xcenter + xx internal_plotx = xcenter + radius
ploty = ycenter + yy ploty = ycenter + yy
internal_plot(ploty) internal_plot(ploty)
internal_plotx = xcenter - xx internal_plotx = xcenter - radius
internal_plot(ploty) internal_plot(ploty)
internal_plotx = xcenter + xx internal_plotx = xcenter + radius
ploty = ycenter - yy ploty = ycenter - yy
internal_plot(ploty) internal_plot(ploty)
internal_plotx = xcenter - xx internal_plotx = xcenter - radius
internal_plot(ploty) internal_plot(ploty)
internal_plotx = xcenter + yy internal_plotx = xcenter + yy
ploty = ycenter + xx ploty = ycenter + radius
internal_plot(ploty) internal_plot(ploty)
internal_plotx = xcenter - yy internal_plotx = xcenter - yy
internal_plot(ploty) internal_plot(ploty)
internal_plotx = xcenter + yy internal_plotx = xcenter + yy
ploty = ycenter - xx ploty = ycenter - radius
internal_plot(ploty) internal_plot(ploty)
internal_plotx = xcenter - yy internal_plotx = xcenter - yy
internal_plot(ploty) internal_plot(ploty)
yy++ yy++
if decisionOver2<=0 if decisionOver2<=0
decisionOver2 += 2*yy+1 decisionOver2 += (yy as word)*2+1
else { else {
xx-- radius--
decisionOver2 += 2*(yy-xx)+1 decisionOver2 += (yy as word -radius)*2+1
} }
} }
} }
sub disc(uword xcenter, ubyte ycenter, ubyte radius) { sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm, filled ; Midpoint algorithm, filled
ubyte xx = radius if radius==0
ubyte yy = 0 return
byte decisionOver2 = 1-xx as byte ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
while xx>=yy { while radius>=yy {
ubyte ycenter_plus_yy = ycenter + yy horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
ubyte ycenter_min_yy = ycenter - yy horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
ubyte ycenter_plus_xx = ycenter + xx horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
ubyte ycenter_min_xx = ycenter - xx horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
for internal_plotx in xcenter to xcenter+xx {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
}
for internal_plotx in xcenter-xx to xcenter-1 {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
}
for internal_plotx in xcenter to xcenter+yy {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_min_xx)
}
for internal_plotx in xcenter-yy to xcenter {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_min_xx)
}
yy++ yy++
if decisionOver2<=0 if decisionOver2<=0
decisionOver2 += 2*yy+1 decisionOver2 += (yy as word)*2+1
else { else {
xx-- radius--
decisionOver2 += 2*(yy-xx)+1 decisionOver2 += (yy as word -radius)*2+1
} }
} }
} }
@ -183,11 +301,11 @@ graphics {
; @(addr) |= ormask[lsb(px) & 7] ; @(addr) |= ormask[lsb(px) & 7]
; } ; }
asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) { inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
%asm {{ %asm {{
stx internal_plotx stx graphics.internal_plotx
sty internal_plotx+1 sty graphics.internal_plotx+1
jmp internal_plot jsr graphics.internal_plot
}} }}
} }
@ -239,6 +357,17 @@ _y_lookup_hi .byte >_plot_y_values
}} }}
} }
asmsub get_y_lookup(ubyte y @Y) -> uword @AY {
%asm {{
lda internal_plot._y_lookup_lo,y
pha
lda internal_plot._y_lookup_hi,y
tay
pla
rts
}}
}
} }

View File

@ -11,7 +11,7 @@ c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte &ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte &ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec &ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $90 ; kernel status variable for I/O &ubyte STATUS = $90 ; kernal status variable for I/O
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ) &ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ) &ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
@ -178,7 +178,7 @@ c64 {
; ---- C64 ROM kernal routines ---- ; ---- C64 ROM kernal routines ----
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead) romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
@ -225,6 +225,41 @@ romsub $FFF3 = IOBASE() -> uword @ XY ; read base addr
; ---- end of C64 ROM kernal routines ---- ; ---- end of C64 ROM kernal routines ----
; ---- utilities -----
asmsub STOP2() -> ubyte @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
%asm {{
txa
pha
jsr c64.STOP
beq +
pla
tax
lda #0
rts
+ pla
tax
lda #1
rts
}}
}
asmsub RDTIM16() -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits for convenience
%asm {{
stx P8ZP_SCRATCH_REG
jsr c64.RDTIM
pha
txa
tay
pla
ldx P8ZP_SCRATCH_REG
rts
}}
}
; ---- C64 specific system utility routines: ---- ; ---- C64 specific system utility routines: ----
@ -259,17 +294,13 @@ asmsub init_system() {
}} }}
} }
asmsub reset_system() { asmsub init_system_phase2() {
; Soft-reset the system back to Basic prompt.
%asm {{ %asm {{
sei rts ; no phase 2 steps on the C64
lda #14
sta $01 ; bank the kernal in
jmp (c64.RESET_VEC)
}} }}
} }
asmsub disable_runstop_and_charsetswitch() { asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{ %asm {{
lda #$80 lda #$80
sta 657 ; disable charset switching sta 657 ; disable charset switching
@ -279,27 +310,13 @@ asmsub disable_runstop_and_charsetswitch() {
}} }}
} }
asmsub set_irqvec_excl() clobbers(A) { asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sei
lda #<_irq_handler
sta c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
asmsub set_irqvec() clobbers(A) {
%asm {{ %asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei sei
lda #<_irq_handler lda #<_irq_handler
sta c64.CINV sta c64.CINV
@ -308,9 +325,23 @@ asmsub set_irqvec() clobbers(A) {
cli cli
rts rts
_irq_handler jsr _irq_handler_init _irq_handler jsr _irq_handler_init
jsr irq.irq _modified jsr $ffff ; modified
jsr _irq_handler_end jsr _irq_handler_end
jmp c64.IRQDFRT ; continue with normal kernel irq routine lda _use_kernal
bne +
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init _irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine ; save all zp scratch registers and the X register as these might be clobbered by the irq routine
@ -363,7 +394,7 @@ IRQ_SCRATCH_ZPWORD2 .word 0
}} }}
} }
asmsub restore_irqvec() { asmsub restore_irq() clobbers(A) {
%asm {{ %asm {{
sei sei
lda #<c64.IRQDFRT lda #<c64.IRQDFRT
@ -379,8 +410,15 @@ asmsub restore_irqvec() {
}} }}
} }
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) { asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
%asm {{ %asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei sei
jsr _setup_raster_irq jsr _setup_raster_irq
lda #<_raster_irq_handler lda #<_raster_irq_handler
@ -391,12 +429,21 @@ asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
rts rts
_raster_irq_handler _raster_irq_handler
jsr set_irqvec._irq_handler_init jsr set_irq._irq_handler_init
jsr irq.irq _modified jsr $ffff ; modified
jsr set_irqvec._irq_handler_end jsr set_irq._irq_handler_end
lda #$ff lda #$ff
sta c64.VICIRQ ; acknowledge raster irq sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFRT lda set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq _setup_raster_irq
pha pha
@ -420,29 +467,216 @@ _setup_raster_irq
}} }}
} }
asmsub set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) {
%asm {{
sei
jsr set_rasterirq._setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
; ---- end of C64 specific system utility routines ---- ; ---- end of C64 specific system utility routines ----
}
sys {
; ------- lowlevel system routines --------
const ubyte target = 64 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
%asm {{
sei
lda #14
sta $01 ; bank the kernal in
jmp (c64.RESET_VEC)
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
}
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1 ; source in ZP
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2 ; target in ZP
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
cpy #0
bne _longcopy
; copy <= 255 bytes
tay
bne _copyshort
rts ; nothing to copy
_copyshort
; decrease source and target pointers so we can simply index by Y
lda P8ZP_SCRATCH_W1
bne +
dec P8ZP_SCRATCH_W1+1
+ dec P8ZP_SCRATCH_W1
lda P8ZP_SCRATCH_W2
bne +
dec P8ZP_SCRATCH_W2+1
+ dec P8ZP_SCRATCH_W2
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
dey
bne -
rts
_longcopy
sta P8ZP_SCRATCH_B1 ; lsb(count) = remainder in last page
tya
tax ; x = num pages (1+)
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dex
bne -
ldy P8ZP_SCRATCH_B1
bne _copyshort
rts
}}
}
asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
%asm {{
ldy cx16.r0
sty P8ZP_SCRATCH_W1
ldy cx16.r0+1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r1
ldy cx16.r1+1
jmp prog8_lib.memset
}}
}
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers(A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
jmp prog8_lib.memsetw
}}
}
inline asmsub rsave() {
; save cpu status flag and all registers A, X, Y.
; see http://6502.org/tutorials/register_preservation.html
%asm {{
php
sta P8ZP_SCRATCH_REG
pha
txa
pha
tya
pha
lda P8ZP_SCRATCH_REG
}}
}
inline asmsub rrestore() {
; restore all registers and cpu status flag
%asm {{
pla
tay
pla
tax
pla
plp
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
jsr c64.CLRCHN ; reset i/o channels
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
ldy #>prog8_program_end
}}
}
}
cx16 {
; the sixteen virtual 16-bit registers that the CX16 has defined in the zeropage
; they are simulated on the C64 as well but their location in memory is different
; (because there's no room for them in the zeropage)
; they are allocated at the bottom of the eval-stack (should be ample space unless
; you're doing insane nesting of expressions...)
&uword r0 = $cf00
&uword r1 = $cf02
&uword r2 = $cf04
&uword r3 = $cf06
&uword r4 = $cf08
&uword r5 = $cf0a
&uword r6 = $cf0c
&uword r7 = $cf0e
&uword r8 = $cf10
&uword r9 = $cf12
&uword r10 = $cf14
&uword r11 = $cf16
&uword r12 = $cf18
&uword r13 = $cf1a
&uword r14 = $cf1c
&uword r15 = $cf1e
} }

View File

@ -16,7 +16,30 @@ const ubyte DEFAULT_HEIGHT = 25
sub clear_screen() { sub clear_screen() {
clear_screenchars(' ') txt.chrout(147)
}
sub home() {
txt.chrout(19)
}
sub nl() {
txt.chrout('\n')
}
sub spc() {
txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
sec
jsr c64.PLOT
tay
clc
jmp c64.PLOT
}}
} }
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) { asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
@ -38,13 +61,13 @@ asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors) ; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address) ; (assumes screen matrix is at the default address)
%asm {{ %asm {{
ldy #0 ldy #250
_loop sta c64.Screen,y - sta c64.Screen+250*0-1,y
sta c64.Screen+$0100,y sta c64.Screen+250*1-1,y
sta c64.Screen+$0200,y sta c64.Screen+250*2-1,y
sta c64.Screen+$02e8,y sta c64.Screen+250*3-1,y
iny dey
bne _loop bne -
rts rts
}} }}
} }
@ -53,13 +76,13 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
; ---- clear the character screen colors with the given color (leaves characters). ; ---- clear the character screen colors with the given color (leaves characters).
; (assumes color matrix is at the default address) ; (assumes color matrix is at the default address)
%asm {{ %asm {{
ldy #0 ldy #250
_loop sta c64.Colors,y - sta c64.Colors+250*0-1,y
sta c64.Colors+$0100,y sta c64.Colors+250*1-1,y
sta c64.Colors+$0200,y sta c64.Colors+250*2-1,y
sta c64.Colors+$02e8,y sta c64.Colors+250*3-1,y
iny dey
bne _loop bne -
rts rts
}} }}
} }
@ -83,29 +106,31 @@ asmsub scroll_left (ubyte alsocolors @ Pc) clobbers(A, Y) {
%asm {{ %asm {{
stx P8ZP_SCRATCH_REG stx P8ZP_SCRATCH_REG
bcs + bcc _scroll_screen
jmp _scroll_screen
+ ; scroll the color memory + ; scroll the screen and the color memory
ldx #0 ldx #0
ldy #38 ldy #38
- -
.for row=0, row<=24, row+=1 .for row=0, row<=24, row+=1
lda c64.Colors + 40*row + 1,x lda c64.Screen + 40*row + 1,x
sta c64.Colors + 40*row,x sta c64.Screen + 40*row + 0,x
.next lda c64.Colors + 40*row + 1,x
sta c64.Colors + 40*row + 0,x
.next
inx inx
dey dey
bpl - bpl -
rts
_scroll_screen ; scroll the screen memory _scroll_screen ; scroll only the screen memory
ldx #0 ldx #0
ldy #38 ldy #38
- -
.for row=0, row<=24, row+=1 .for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 1,x lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row,x sta c64.Screen + 40*row + 0,x
.next .next
inx inx
dey dey
bpl - bpl -
@ -121,26 +146,28 @@ asmsub scroll_right (ubyte alsocolors @ Pc) clobbers(A) {
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
%asm {{ %asm {{
stx P8ZP_SCRATCH_REG stx P8ZP_SCRATCH_REG
bcs + bcc _scroll_screen
jmp _scroll_screen
+ ; scroll the color memory + ; scroll the screen and the color memory
ldx #38 ldx #38
- -
.for row=0, row<=24, row+=1 .for row=0, row<=24, row+=1
lda c64.Colors + 40*row + 0,x lda c64.Screen + 40*row + 0,x
sta c64.Colors + 40*row + 1,x sta c64.Screen + 40*row + 1,x
.next lda c64.Colors + 40*row + 0,x
sta c64.Colors + 40*row + 1,x
.next
dex dex
bpl - bpl -
rts
_scroll_screen ; scroll the screen memory _scroll_screen ; scroll only the screen memory
ldx #38 ldx #38
- -
.for row=0, row<=24, row+=1 .for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 0,x lda c64.Screen + 40*row + 0,x
sta c64.Screen + 40*row + 1,x sta c64.Screen + 40*row + 1,x
.next .next
dex dex
bpl - bpl -
@ -155,26 +182,28 @@ asmsub scroll_up (ubyte alsocolors @ Pc) clobbers(A) {
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
%asm {{ %asm {{
stx P8ZP_SCRATCH_REG stx P8ZP_SCRATCH_REG
bcs + bcc _scroll_screen
jmp _scroll_screen
+ ; scroll the color memory + ; scroll the screen and the color memory
ldx #39 ldx #39
- -
.for row=1, row<=24, row+=1 .for row=1, row<=24, row+=1
lda c64.Colors + 40*row,x lda c64.Screen + 40*row,x
sta c64.Colors + 40*(row-1),x sta c64.Screen + 40*(row-1),x
.next lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row-1),x
.next
dex dex
bpl - bpl -
rts
_scroll_screen ; scroll the screen memory _scroll_screen ; scroll only the screen memory
ldx #39 ldx #39
- -
.for row=1, row<=24, row+=1 .for row=1, row<=24, row+=1
lda c64.Screen + 40*row,x lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x sta c64.Screen + 40*(row-1),x
.next .next
dex dex
bpl - bpl -
@ -189,26 +218,28 @@ asmsub scroll_down (ubyte alsocolors @ Pc) clobbers(A) {
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
%asm {{ %asm {{
stx P8ZP_SCRATCH_REG stx P8ZP_SCRATCH_REG
bcs + bcc _scroll_screen
jmp _scroll_screen
+ ; scroll the color memory + ; scroll the screen and the color memory
ldx #39 ldx #39
- -
.for row=23, row>=0, row-=1 .for row=23, row>=0, row-=1
lda c64.Colors + 40*row,x lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row+1),x sta c64.Colors + 40*(row+1),x
.next lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
dex dex
bpl - bpl -
rts
_scroll_screen ; scroll the screen memory _scroll_screen ; scroll only the screen memory
ldx #39 ldx #39
- -
.for row=23, row>=0, row-=1 .for row=23, row>=0, row-=1
lda c64.Screen + 40*row,x lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x sta c64.Screen + 40*(row+1),x
.next .next
dex dex
bpl - bpl -
@ -555,7 +586,7 @@ _colormod sta $ffff ; modified
} }
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) { asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register. ; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{ %asm {{
stx P8ZP_SCRATCH_REG stx P8ZP_SCRATCH_REG
tax tax

View File

@ -1,30 +1,518 @@
; Prog8 definitions for number conversions routines. ; Number conversions routines.
; ;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
conv { conv {
; ----- number conversions to decimal strings ; ----- number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X { str string_out = "????????????????" ; result buffer for the string conversion routines
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{ asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
ldy #uword2decimal.ASCII_0_OFFSET ; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
bne uword2decimal.hex_try200 %asm {{
phx
jsr conv.ubyte2decimal
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
plx
rts
}}
}
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
phx
ldy #0
sty P8ZP_SCRATCH_B1
jsr conv.ubyte2decimal
_output_byte_digits
; hundreds?
cpy #'0'
beq +
pha
tya
ldy P8ZP_SCRATCH_B1
sta string_out,y
pla
inc P8ZP_SCRATCH_B1
; tens?
+ ldy P8ZP_SCRATCH_B1
cmp #'0'
beq +
sta string_out,y
iny
+ ; ones.
txa
sta string_out,y
iny
lda #0
sta string_out,y
plx
rts
}}
}
asmsub str_b (byte value @ A) clobbers(A,Y) {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
phx
ldy #0
sty P8ZP_SCRATCH_B1
cmp #0
bpl +
pha
lda #'-'
sta string_out
inc P8ZP_SCRATCH_B1
pla
+ jsr conv.byte2decimal
bra str_ub._output_byte_digits
}}
}
asmsub str_ubhex (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in hex string form
%asm {{
jsr conv.ubyte2hex
sta string_out
sty string_out+1
lda #0
sta string_out+2
rts
}}
}
asmsub str_ubbin (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in binary string form
%asm {{
sta P8ZP_SCRATCH_B1
ldy #0
sty string_out+8
ldy #7
- lsr P8ZP_SCRATCH_B1
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out,y
dey
bpl -
rts
}}
}
asmsub str_uwbin (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in binary string form
%asm {{
sta P8ZP_SCRATCH_REG
tya
jsr str_ubbin
ldy #0
sty string_out+16
ldy #7
- lsr P8ZP_SCRATCH_REG
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out+8,y
dey
bpl -
rts
}}
}
asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
%asm {{
pha
tya
jsr conv.ubyte2hex
sta string_out
sty string_out+1
pla
jsr conv.ubyte2hex
sta string_out+2
sty string_out+3
lda #0
sta string_out+4
rts
}}
}
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
phx
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
sta string_out,y
beq +
iny
bne -
+ plx
rts
}}
}
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
%asm {{
phx
jsr conv.uword2decimal
ldx #0
_output_digits
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit sta string_out,x
inx
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
_end lda #0
sta string_out,x
plx
rts
_allzero lda #'0'
sta string_out,x
inx
bne _end
}}
}
asmsub str_w (word value @ AY) clobbers(A,Y) {
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
%asm {{
cpy #0
bpl str_uw
phx
pha
lda #'-'
sta string_out
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jsr conv.uword2decimal
ldx #1
bne str_uw._output_digits
}}
}
; ---- string conversion to numbers -----
asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
; -- parses a string into a 16 bit unsigned number. String may be in decimal, hex or binary format.
; (the latter two require a $ or % prefix to be recognised)
; (any non-digit character will terminate the number string that is parsed)
; returns amount of processed characters in A, and the parsed number will be in cx16.r15.
; if the string was invalid, 0 will be returned in A.
%asm {{
pha
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
ldy P8ZP_SCRATCH_W1+1
cmp #'$'
beq _hex
cmp #'%'
beq _bin
pla
jsr str2uword
jmp _result
_hex pla
jsr hex2uword
jmp _result
_bin pla
jsr bin2uword
_result
pha
lda cx16.r15
sta P8ZP_SCRATCH_B1 ; result value
pla
sta cx16.r15
sty cx16.r15+1
lda P8ZP_SCRATCH_B1
rts rts
}} }}
} }
asmsub uword2decimal (uword value @ AY) -> ubyte @Y, ubyte @A, ubyte @X { inline asmsub str2ubyte(str string @AY) clobbers(Y) -> ubyte @A {
; ---- convert 16 bit uword in A/Y to decimal ; -- returns in A the unsigned byte value of the string number argument in AY
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes ; the number may NOT be preceded by a + sign and may NOT contain spaces
; (these are terminated by a zero byte so they can be easily printed) ; (any non-digit character will terminate the number string that is parsed)
; also returns Y = 100's, A = 10's, X = 1's ; result in A, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
jsr conv.str2uword
}}
}
%asm {{ inline asmsub str2byte(str string @AY) clobbers(Y) -> ubyte @A {
; -- returns in A the signed byte value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; result in A, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
jsr conv.str2word
}}
}
asmsub str2uword(str string @AY) -> uword @AY {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
_result = P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty _result
sty _result+1
sty cx16.r15+1
_loop
lda (P8ZP_SCRATCH_W2),y
sec
sbc #48
bpl _digit
_done
sty cx16.r15
lda _result
ldy _result+1
rts
_digit
cmp #10
bcs _done
; add digit to result
pha
jsr _result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _loop
; never reached
_result_times_10 ; (W*4 + W)*2
lda _result+1
sta P8ZP_SCRATCH_REG
lda _result
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
clc
adc _result
sta _result
lda P8ZP_SCRATCH_REG
adc _result+1
asl _result
rol a
sta _result+1
rts
}}
}
asmsub str2word(str string @AY) -> word @AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
_result = P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty _result
sty _result+1
sty _negative
sty cx16.r15+1
lda (P8ZP_SCRATCH_W2),y
cmp #'+'
bne +
iny
+ cmp #'-'
bne _parse
inc _negative
iny
_parse lda (P8ZP_SCRATCH_W2),y
sec
sbc #48
bpl _digit
_done
sty cx16.r15
lda _negative
beq +
sec
lda #0
sbc _result
sta _result
lda #0
sbc _result+1
sta _result+1
+ lda _result
ldy _result+1
rts
_digit
cmp #10
bcs _done
; add digit to result
pha
jsr str2uword._result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _parse
; never reached
_negative .byte 0
}}
}
asmsub hex2uword(str string @AY) -> uword @AY {
; -- hexadecimal string (with or without '$') to uword.
; string may be in petscii or c64-screencode encoding.
; stops parsing at the first character that's not a hex digit (except leading $)
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sty cx16.r15+1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'$'
bne _loop
iny
_loop
lda #0
sta P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #7 ; screencode letters A-F are 1-6
bcc _add_letter
cmp #'g'
bcs _stop
cmp #'a'
bcs _add_letter
cmp #'0'
bcc _stop
cmp #'9'+1
bcs _stop
_calc
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #$0f
clc
adc P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
iny
bne _loop
_stop
sty cx16.r15
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
_add_letter
pha
lda #9
sta P8ZP_SCRATCH_B1
pla
jmp _calc
}}
}
asmsub bin2uword(str string @AY) -> uword @AY {
; -- binary string (with or without '%') to uword.
; stops parsing at the first character that's not a 0 or 1. (except leading %)
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
sty cx16.r15+1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'%'
bne _loop
iny
_loop
lda (P8ZP_SCRATCH_W2),y
cmp #'0'
bcc _stop
cmp #'2'
bcs _stop
_first asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
iny
bne _loop
_stop
sty cx16.r15
lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
}}
}
; ----- low level number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
rts
}}
}
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
%asm {{
;Convert 16 bit Hex to Decimal (0-65535) Rev 2 ;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples ;By Omegamatrix Further optimizations by tepples
@ -193,11 +681,7 @@ decOnes .byte 0
}} }}
} }
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ----- utility functions ----
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X) ; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself! ; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{ %asm {{
@ -210,7 +694,7 @@ asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
}} }}
} }
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y { asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y) ; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
%asm {{ %asm {{
stx P8ZP_SCRATCH_REG stx P8ZP_SCRATCH_REG
@ -232,7 +716,7 @@ _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
}} }}
} }
asmsub uword2hex (uword value @ AY) clobbers(A,Y) { asmsub uword2hex (uword value @AY) clobbers(A,Y) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated) ; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{ %asm {{
sta P8ZP_SCRATCH_REG sta P8ZP_SCRATCH_REG
@ -245,211 +729,8 @@ asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
sta output+2 sta output+2
sty output+3 sty output+3
rts rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier) output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
}} }}
} }
asmsub str2ubyte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the unsigned byte value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; TODO implement optimized custom version of this instead of simply reusing str2uword
%asm {{
jmp str2uword
}}
}
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the signed byte value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; TODO implement optimized custom version of this instead of simply reusing str2word
%asm {{
jmp str2word
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
_result = P8ZP_SCRATCH_W2
sta _mod+1
sty _mod+2
ldy #0
sty _result
sty _result+1
_mod lda $ffff,y ; modified
sec
sbc #48
bpl +
_done ; return result
lda _result
ldy _result+1
rts
+ cmp #10
bcs _done
; add digit to result
pha
jsr _result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _mod
; never reached
_result_times_10 ; (W*4 + W)*2
lda _result+1
sta P8ZP_SCRATCH_REG
lda _result
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
clc
adc _result
sta _result
lda P8ZP_SCRATCH_REG
adc _result+1
asl _result
rol a
sta _result+1
rts
}}
}
asmsub str2word(str string @ AY) -> word @ AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
_result = P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
sty _result
sty _result+1
sty _negative
lda (P8ZP_SCRATCH_W1),y
cmp #'+'
bne +
iny
+ cmp #'-'
bne _parse
inc _negative
iny
_parse lda (P8ZP_SCRATCH_W1),y
sec
sbc #48
bpl _digit
_done ; return result
lda _negative
beq +
sec
lda #0
sbc _result
sta _result
lda #0
sbc _result+1
sta _result+1
+ lda _result
ldy _result+1
rts
_digit cmp #10
bcs _done
; add digit to result
pha
jsr str2uword._result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _parse
; never reached
_negative .byte 0
}}
}
asmsub hex2uword(str string @ AY) -> uword @AY {
; -- hexadecimal string with or without '$' to uword.
; string may be in petscii or c64-screencode encoding.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
sty P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'$'
beq _skip
cmp #7
bcc _add_nine
cmp #'9'
beq _calc
bcs _add_nine
_calc asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #$0f
clc
adc P8ZP_SCRATCH_B1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
_skip inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
_add_nine ldy #9
sty P8ZP_SCRATCH_B1
bne _calc
}}
}
asmsub bin2uword(str string @ AY) -> uword @AY {
; -- binary string with or without '%' to uword.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop lda (P8ZP_SCRATCH_W2),y
beq _stop
cmp #'%'
beq +
asl P8ZP_SCRATCH_W1
rol P8ZP_SCRATCH_W1+1
and #1
ora P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_stop lda P8ZP_SCRATCH_W1
ldy P8ZP_SCRATCH_W1+1
rts
}}
}
} }

View File

@ -64,6 +64,7 @@ romsub $fe7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += sign
romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY romsub $fe81 = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY
romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1) romsub $fe8a = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1 romsub $fe8d = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
; note: there is no FPWR() on the Cx16
romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1 romsub $fe93 = NEGOP() clobbers(A) ; switch the sign of fac1
romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1) romsub $fe96 = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator romsub $fe9f = RND2(byte value @A) clobbers(A,X,Y) ; fac1 = RND(A) float random number generator
@ -98,9 +99,9 @@ _flt65536 .byte 145,0,0,0,0 ; 65536.0
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) { asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1 ; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{ %asm {{
sta P8ZP_SCRATCH_W2 sta P8ZP_SCRATCH_B1
tya tya
ldy P8ZP_SCRATCH_W2 ldy P8ZP_SCRATCH_B1
jmp GIVAYF ; this uses the inverse order, Y/A jmp GIVAYF ; this uses the inverse order, Y/A
}} }}
} }
@ -109,9 +110,9 @@ asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y ; ---- fac1 to signed word in A/Y
%asm {{ %asm {{
jsr FTOSWORDYA ; note the inverse Y/A order jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_REG sta P8ZP_SCRATCH_B1
tya tya
ldy P8ZP_SCRATCH_REG ldy P8ZP_SCRATCH_B1
rts rts
}} }}
} }
@ -149,5 +150,6 @@ sub print_f (float value) {
} }
%asminclude "library:c64/floats.asm", "" %asminclude "library:c64/floats.asm", ""
%asminclude "library:c64/floats_funcs.asm", ""
} }

View File

@ -0,0 +1,976 @@
%target cx16
; Bitmap pixel graphics routines for the CommanderX16
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
;
; No text layer is currently shown, text can be drawn as part of the bitmap itself.
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself.
; Note: this library implements code for various resolutions and color depths. This takes up memory.
; If you're memory constrained you should probably not use this built-in library,
; but make a copy in your project only containing the code for the required resolution.
;
;
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 monochrome
; mode 2 = bitmap 320 x 240 x 4c (TODO not yet implemented)
; mode 3 = bitmap 320 x 240 x 16c (TODO not yet implemented)
; mode 4 = bitmap 320 x 240 x 256c
; mode 5 = bitmap 640 x 480 monochrome
; mode 6 = bitmap 640 x 480 x 4c
; higher color dephts in highres are not supported due to lack of VRAM
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
gfx2 {
; read-only control variables:
ubyte active_mode = 0
uword width = 0
uword height = 0
ubyte bpp = 0
ubyte monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
sub screen_mode(ubyte mode) {
when mode {
1 -> {
; lores monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
width = 320
height = 240
bpp = 1
}
; TODO modes 2, 3 not yet implemented
4 -> {
; lores 256c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
cx16.VERA_L1_CONFIG = %00000111
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = 0
width = 320
height = 240
bpp = 8
}
5 -> {
; highres monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000100
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
bpp = 1
}
6 -> {
; highres 4c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000101
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
bpp = 2
}
else -> {
; back to default text mode and colors
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
c64.CINT() ; back to text mode
width = 0
height = 0
bpp = 0
mode = 0
}
}
active_mode = mode
if bpp
clear_screen()
}
sub clear_screen() {
monochrome_stipple(false)
position(0, 0)
when active_mode {
1 -> {
; lores monochrome
repeat 240/2/8
cs_innerloop640()
}
; TODO mode 2, 3
4 -> {
; lores 256c
repeat 240/2
cs_innerloop640()
}
5 -> {
; highres monochrome
repeat 480/8
cs_innerloop640()
}
6 -> {
; highres 4c
repeat 480/4
cs_innerloop640()
}
; modes 7 and 8 not supported due to lack of VRAM
}
position(0, 0)
}
sub monochrome_stipple(ubyte enable) {
monochrome_dont_stipple_flag = not enable
}
sub rect(uword x, uword y, uword width, uword height, ubyte color) {
if width==0 or height==0
return
horizontal_line(x, y, width, color)
if height==1
return
horizontal_line(x, y+height-1, width, color)
vertical_line(x, y+1, height-2, color)
if width==1
return
vertical_line(x+width-1, y+1, height-2, color)
}
sub fillrect(uword x, uword y, uword width, uword height, ubyte color) {
if width==0
return
repeat height {
horizontal_line(x, y, width, color)
y++
}
}
sub horizontal_line(uword x, uword y, uword length, ubyte color) {
if length==0
return
when active_mode {
1, 5 -> {
; monochrome modes, either resolution
ubyte separate_pixels = (8-lsb(x)) & 7
if separate_pixels as uword > length
separate_pixels = lsb(length)
repeat separate_pixels {
; TODO optimize this by writing a masked byte in 1 go
plot(x, y, color)
x++
}
length -= separate_pixels
if length {
position(x, y)
separate_pixels = lsb(length) & 7
x += length & $fff8
%asm {{
lsr length+1
ror length
lsr length+1
ror length
lsr length+1
ror length
lda color
bne +
ldy #0 ; black
bra _loop
+ lda monochrome_dont_stipple_flag
beq _stipple
ldy #255 ; don't stipple
bra _loop
_stipple lda y
and #1 ; determine stipple pattern to use
bne +
ldy #%01010101
bra _loop
+ ldy #%10101010
_loop lda length
ora length+1
beq _done
sty cx16.VERA_DATA0
lda length
bne +
dec length+1
+ dec length
bra _loop
_done
}}
repeat separate_pixels {
; TODO optimize this by writing a masked byte in 1 go
plot(x, y, color)
x++
}
}
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
}
4 -> {
; lores 256c
position(x, y)
%asm {{
lda color
phx
ldx length+1
beq +
ldy #0
- sta cx16.VERA_DATA0
iny
bne -
dex
bne -
+ ldy length ; remaining
beq +
- sta cx16.VERA_DATA0
dey
bne -
+ plx
}}
}
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
color &= 3
ubyte[4] colorbits
ubyte ii
for ii in 3 downto 0 {
colorbits[ii] = color
color <<= 2
}
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
lda cx16.VERA_ADDR_H
and #%00000111 ; no auto advance
sta cx16.VERA_ADDR_H
stz cx16.VERA_CTRL ; setup vera addr 0
lda cx16.r1
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
phx
ldx x
}}
repeat length {
%asm {{
txa
and #3
tay
lda cx16.VERA_DATA0
and gfx2.plot.mask4c,y
ora colorbits,y
sta cx16.VERA_DATA0
cpy #%00000011 ; next vera byte?
bne +
inc cx16.VERA_ADDR_L
bne +
inc cx16.VERA_ADDR_M
+ bne +
inc cx16.VERA_ADDR_H
+ inx ; next pixel
}}
}
%asm {{
plx
}}
}
}
}
sub vertical_line(uword x, uword y, uword height, ubyte color) {
position(x,y)
when active_mode {
1, 5 -> {
; monochrome, either resolution
; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if active_mode>=5
cx16.r14 = 640/8
else
cx16.r14 = 320/8
if color {
if monochrome_dont_stipple_flag {
repeat height {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera ptr to go to the next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
}}
}
} else {
; stippling.
height = (height+1)/2 ; TODO is the line sometimes 1 pixel too long now because of rounding?
%asm {{
lda x
eor y
and #1
bne +
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera ptr to go to the next line for correct stipple pattern
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
+
asl cx16.r14
ldy height
beq +
- lda cx16.VERA_DATA0
ora cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera data ptr to go to the next-next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
dey
bne -
+
}}
}
} else {
cx16.r15 = ~cx16.r15
repeat height {
%asm {{
lda cx16.VERA_DATA0
and cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera data ptr to go to the next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; the bitmap size is small enough to not have to deal with the _H part:
; lda cx16.VERA_ADDR_H
; adc #0
; sta cx16.VERA_ADDR_H
}}
}
}
}
4 -> {
; lores 256c
; set vera auto-increment to 320 pixel increment (=next line)
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
%asm {{
ldy height
beq +
lda color
- sta cx16.VERA_DATA0
dey
bne -
+
}}
}
6 -> {
; highres 4c
; note for this mode we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
; TODO optimize the loop in pure assembly
color &= 3
color <<= gfx2.plot.shift4c[lsb(x) & 3]
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
repeat height {
ubyte value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask | color
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
%asm {{
; 24 bits add 160 (640/4)
clc
lda cx16.r0
adc #640/4
sta cx16.r0
lda cx16.r0+1
adc #0
sta cx16.r0+1
bcc +
inc cx16.r1
+
}}
}
}
}
}
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
; Bresenham algorithm.
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
; TODO there are some slight errors at the first/last pixels in certain slopes...
if y1>y2 {
; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2)
swap(y1, y2)
}
word @zp dx = x2-x1 as word
word @zp dy = y2-y1 as word
if dx==0 {
vertical_line(x1, y1, abs(dy)+1 as uword, color)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, y1, abs(dx)+1 as uword, color)
return
}
; TODO rewrite the rest in optimized assembly (or reuse GRAPH_draw_line if we can get the FB replacement vector layer working)
word @zp d = 0
ubyte positive_ix = true
if dx < 0 {
dx = -dx
positive_ix = false
}
dx *= 2
dy *= 2
cx16.r14 = x1 ; internal plot X
if dx >= dy {
if positive_ix {
repeat {
plot(cx16.r14, y1, color)
if cx16.r14==x2
return
cx16.r14++
d += dy
if d > dx {
y1++
d -= dx
}
}
} else {
repeat {
plot(cx16.r14, y1, color)
if cx16.r14==x2
return
cx16.r14--
d += dy
if d > dx {
y1++
d -= dx
}
}
}
}
else {
if positive_ix {
repeat {
plot(cx16.r14, y1, color)
if y1 == y2
return
y1++
d += dx
if d > dy {
cx16.r14++
d -= dy
}
}
} else {
repeat {
plot(cx16.r14, y1, color)
if y1 == y2
return
y1++
d += dx
if d > dy {
cx16.r14--
d -= dy
}
}
}
}
}
sub circle(uword @zp xcenter, uword @zp ycenter, ubyte radius, ubyte color) {
; Midpoint algorithm.
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-xx
; R14 = internal plot X
; R15 = internal plot Y
while xx>=yy {
cx16.r14 = xcenter + xx
cx16.r15 = ycenter + yy
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter + xx
cx16.r15 = ycenter - yy
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter - xx
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter + yy
cx16.r15 = ycenter + xx
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter + yy
cx16.r15 = ycenter - xx
plot(cx16.r14, cx16.r15, color)
cx16.r14 = xcenter - yy
plot(cx16.r14, cx16.r15, color)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
xx--
decisionOver2 += (yy as word -xx)*2+1
}
}
}
sub disc(uword @zp xcenter, uword @zp ycenter, ubyte @zp radius, ubyte color) {
; Midpoint algorithm, filled
if radius==0
return
ubyte @zp yy = 0
word @zp decisionOver2 = (1 as word)-radius
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*$0002+1, color)
horizontal_line(xcenter-radius, ycenter-yy, radius*$0002+1, color)
horizontal_line(xcenter-yy, ycenter+radius, yy*$0002+1, color)
horizontal_line(xcenter-yy, ycenter-radius, yy*$0002+1, color)
yy++
if decisionOver2<=0
decisionOver2 += (yy as word)*2+1
else {
radius--
decisionOver2 += (yy as word -radius)*2+1
}
}
}
sub plot(uword @zp x, uword y, ubyte color) {
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100]
ubyte[4] shift4c = [6,4,2,0]
uword addr
ubyte value
when active_mode {
1 -> {
; lores monochrome
%asm {{
lda x
eor y
ora monochrome_dont_stipple_flag
and #1
}}
if_nz {
addr = x/8 + y*(320/8)
value = bits[lsb(x)&7]
if color
cx16.vpoke_or(0, addr, value)
else {
value = ~value
cx16.vpoke_and(0, addr, value)
}
}
}
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.vpoke(lsb(cx16.r1), cx16.r0, color)
; activate vera auto-increment mode so next_pixel() can be used after this
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | %00010000
color = cx16.VERA_DATA0
}
5 -> {
; highres monochrome
%asm {{
lda x
eor y
ora monochrome_dont_stipple_flag
and #1
}}
if_nz {
addr = x/8 + y*(640/8)
value = bits[lsb(x)&7]
if color
cx16.vpoke_or(0, addr, value)
else {
value = ~value
cx16.vpoke_and(0, addr, value)
}
}
}
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
color &= 3
color <<= shift4c[lsb(x) & 3]
; TODO optimize the vera memory manipulation in pure assembly
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask4c[lsb(x) & 3] | color
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
}
}
}
sub position(uword @zp x, uword y) {
ubyte bank
when active_mode {
1 -> {
; lores monochrome
cx16.r0 = y*(320/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
; TODO modes 2,3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 0, 1)
}
5 -> {
; highres monochrome
cx16.r0 = y*(640/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
6 -> {
; highres 4c
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 0, 1)
}
}
}
inline asmsub next_pixel(ubyte color @A) {
; -- sets the next pixel byte to the graphics chip.
; for 8 bpp screens this will plot 1 pixel.
; for 1 bpp screens it will plot 8 pixels at once (color = bit pattern).
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
%asm {{
sta cx16.VERA_DATA0
}}
}
asmsub next_pixels(uword pixels @AY, uword amount @R0) clobbers(A, Y) {
; -- sets the next bunch of pixels from a prepared array of bytes.
; for 8 bpp screens this will plot 1 pixel per byte.
; for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte).
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
%asm {{
phx
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx cx16.r0+1
beq +
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta cx16.VERA_DATA0
iny
bne -
inc P8ZP_SCRATCH_W1+1 ; next page of 256 pixels
dex
bne -
+ ldx cx16.r0 ; remaining pixels
beq +
ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta cx16.VERA_DATA0
iny
dex
bne -
+ plx
}}
}
asmsub set_8_pixels_from_bits(ubyte bits @R0, ubyte oncolor @A, ubyte offcolor @Y) {
; this is only useful in 256 color mode where one pixel equals one byte value.
%asm {{
phx
ldx #8
- asl cx16.r0
bcc +
sta cx16.VERA_DATA0
bra ++
+ sty cx16.VERA_DATA0
+ dex
bne -
plx
rts
}}
}
const ubyte charset_orig_bank = $0
const uword charset_orig_addr = $f800 ; in bank 0, so $0f800
const ubyte charset_bank = $1
const uword charset_addr = $f000 ; in bank 1, so $1f000
sub text_charset(ubyte charset) {
; -- make a copy of the selected character set to use with text()
; the charset number is the same as for the cx16.screen_set_charset() ROM function.
; 1 = ISO charset, 2 = PETSCII uppercase+graphs, 3= PETSCII uppercase+lowercase.
cx16.screen_set_charset(charset, 0)
cx16.vaddr(charset_orig_bank, charset_orig_addr, 0, 1)
cx16.vaddr(charset_bank, charset_addr, 1, 1)
repeat 256*8 {
cx16.VERA_DATA1 = cx16.VERA_DATA0
}
}
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to mulitples of 8 ! TODO allow per-pixel horizontal positioning
uword chardataptr
when active_mode {
1, 5 -> {
; monochrome mode, either resolution
cx16.r2 = 40
if active_mode>=5
cx16.r2 = 80
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
position(x,y)
%asm {{
lda cx16.VERA_ADDR_H
and #%111 ; don't auto-increment, we have to do that manually because of the ora
sta cx16.VERA_ADDR_H
lda color
sta P8ZP_SCRATCH_B1
ldy #8
- lda P8ZP_SCRATCH_B1
bne + ; white color, plot normally
lda cx16.VERA_DATA1
eor #255 ; black color, keep only the other pixels
and cx16.VERA_DATA0
bra ++
+ lda cx16.VERA_DATA0
ora cx16.VERA_DATA1
+ sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r2
sta cx16.VERA_ADDR_L
bcc +
inc cx16.VERA_ADDR_M
+ lda x
clc
adc #1
sta x
bcc +
inc x+1
+ dey
bne -
}}
sctextptr++
}
}
4 -> {
; lores 256c
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
; TODO rewrite this inner loop fully in assembly
position(x,y)
y++
%asm {{
phx
ldx #1
lda cx16.VERA_DATA1
sta P8ZP_SCRATCH_B1
ldy #8
- asl P8ZP_SCRATCH_B1
bcc +
stx cx16.VERA_DATA0 ; write a pixel
bra ++
+ lda cx16.VERA_DATA0 ; don't write a pixel, but do advance to the next address
+ dey
bne -
plx
}}
}
x+=8
y-=8
sctextptr++
}
}
6 -> {
; hires 4c
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
repeat 8 {
; TODO rewrite this inner loop fully in assembly
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
repeat 8 {
charbits <<= 1
if_cs
plot(x, y, color)
x++
}
x-=8
chardataptr++
y++
}
x+=8
y-=8
sctextptr++
}
}
}
}
asmsub cs_innerloop640() clobbers(Y) {
%asm {{
ldy #80
- stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
stz cx16.VERA_DATA0
dey
bne -
rts
}}
}
asmsub addr_mul_24_for_highres_4c(uword yy @R2, uword xx @R3) clobbers(A, Y) -> uword @R0, uword @R1 {
; yy * 160 + xx/4 (24 bits calculation)
; 24 bits result is in r0 and r1L (highest byte)
%asm {{
ldy #5
- asl cx16.r2
rol cx16.r2+1
dey
bne -
lda cx16.r2
sta cx16.r0
lda cx16.r2+1
sta cx16.r0+1
asl cx16.r0
rol cx16.r0+1
asl cx16.r0
rol cx16.r0+1
; xx >>= 2 (xx=R3)
lsr cx16.r3+1
ror cx16.r3
lsr cx16.r3+1
ror cx16.r3
; add r2 and xx (r3) to r0 (24-bits)
stz cx16.r1
clc
lda cx16.r0
adc cx16.r2
sta cx16.r0
lda cx16.r0+1
adc cx16.r2+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ clc
lda cx16.r0
adc cx16.r3
sta cx16.r0
lda cx16.r0+1
adc cx16.r3+1
sta cx16.r0+1
bcc +
inc cx16.r1
+
rts
}}
}
asmsub addr_mul_24_for_lores_256c(uword yy @R0, uword xx @AY) clobbers(A) -> uword @R0, ubyte @R1 {
; yy * 320 + xx (24 bits calculation)
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
sta P8ZP_SCRATCH_B1
lda cx16.r0+1
sta cx16.r1
sta P8ZP_SCRATCH_REG
lda cx16.r0
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
sta cx16.r0
lda P8ZP_SCRATCH_B1
clc
adc P8ZP_SCRATCH_REG
sta cx16.r0+1
bcc +
inc cx16.r1
+ ; now add the value to this 24-bits number
lda cx16.r0
clc
adc P8ZP_SCRATCH_W1
sta cx16.r0
lda cx16.r0+1
adc P8ZP_SCRATCH_W1+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ lda cx16.r1
rts
}}
}
}

View File

@ -1,9 +1,13 @@
%target cx16 %target cx16
%import syslib %import syslib
%import textio
; bitmap pixel graphics module for the CommanderX16 ; Bitmap pixel graphics module for the CommanderX16
; wraps the graphics functions that are in ROM. ; wraps the graphics functions that are in ROM.
; only black/white monchrome 320x200 for now. ; only black/white monochrome 320x200 for now. (i.e. truncated at the bottom)
; For full-screen 640x480 or 320x240 graphics, use the "gfx2" module instead. (but that is Cx16-specific)
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
graphics { graphics {
const uword WIDTH = 320 const uword WIDTH = 320
@ -12,22 +16,42 @@ graphics {
sub enable_bitmap_mode() { sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white. ; enable bitmap screen, erase it and set colors to black/white.
void cx16.screen_set_mode($80) void cx16.screen_set_mode($80)
cx16.r0 = 0 cx16.GRAPH_init(0)
cx16.GRAPH_init()
clear_screen(1, 0) clear_screen(1, 0)
} }
sub disable_bitmap_mode() {
; enables text mode, erase the text screen, color white
void cx16.screen_set_mode(2)
txt.fill_screen(' ', 1) ; doesn't seem to fully clear the text screen after returning from gfx mode
}
sub clear_screen(ubyte pixelcolor, ubyte bgcolor) { sub clear_screen(ubyte pixelcolor, ubyte bgcolor) {
cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor) cx16.GRAPH_set_colors(pixelcolor, pixelcolor, bgcolor)
cx16.GRAPH_clear() cx16.GRAPH_clear()
} }
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) { sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
cx16.r0 = x1 cx16.GRAPH_draw_line(x1, y1, x2, y2)
cx16.r1 = y1 }
cx16.r2 = x2
cx16.r3 = y2 sub fillrect(uword x, uword y, uword width, uword height) {
cx16.GRAPH_draw_line() cx16.GRAPH_draw_rect(x, y, width, height, 0, 1)
}
sub rect(uword x, uword y, uword width, uword height) {
cx16.GRAPH_draw_rect(x, y, width, height, 0, 0)
}
sub horizontal_line(uword x, uword y, uword length) {
if length
cx16.GRAPH_draw_line(x, y, x+length-1, y)
}
sub vertical_line(uword x, uword y, uword height) {
if height
cx16.GRAPH_draw_line(x, y, x, y+height-1)
} }
sub circle(uword xcenter, ubyte ycenter, ubyte radius) { sub circle(uword xcenter, ubyte ycenter, ubyte radius) {
@ -35,122 +59,80 @@ graphics {
;cx16.r1 = ycenter - radius/2 ;cx16.r1 = ycenter - radius/2
;cx16.r2 = radius*2 ;cx16.r2 = radius*2
;cx16.r3 = radius*2 ;cx16.r3 = radius*2
;cx16.GRAPH_draw_oval(false) ; TODO currently is not implemented on cx16, does a BRK ;cx16.GRAPH_draw_oval(false) ; currently this call is not implemented on cx16, does a BRK
; Midpoint algorithm ; Midpoint algorithm
if radius==0
return
ubyte @zp xx = radius ubyte @zp xx = radius
ubyte @zp yy = 0 ubyte @zp yy = 0
byte @zp decisionOver2 = 1-xx as byte word @zp decisionOver2 = (1 as word)-xx
while xx>=yy { while xx>=yy {
cx16.r0 = xcenter + xx cx16.r0 = xcenter + xx
cx16.r1 = ycenter + yy cx16.r1 = ycenter + yy
cx16.FB_cursor_position() cx16.FB_cursor_position2()
cx16.FB_set_pixel(1) cx16.FB_set_pixel(1)
cx16.r0 = xcenter - xx cx16.r0 = xcenter - xx
cx16.FB_cursor_position() cx16.FB_cursor_position2()
cx16.FB_set_pixel(1) cx16.FB_set_pixel(1)
cx16.r0 = xcenter + xx cx16.r0 = xcenter + xx
cx16.r1 = ycenter - yy cx16.r1 = ycenter - yy
cx16.FB_cursor_position() cx16.FB_cursor_position2()
cx16.FB_set_pixel(1) cx16.FB_set_pixel(1)
cx16.r0 = xcenter - xx cx16.r0 = xcenter - xx
cx16.FB_cursor_position() cx16.FB_cursor_position2()
cx16.FB_set_pixel(1) cx16.FB_set_pixel(1)
cx16.r0 = xcenter + yy cx16.r0 = xcenter + yy
cx16.r1 = ycenter + xx cx16.r1 = ycenter + xx
cx16.FB_cursor_position() cx16.FB_cursor_position2()
cx16.FB_set_pixel(1) cx16.FB_set_pixel(1)
cx16.r0 = xcenter - yy cx16.r0 = xcenter - yy
cx16.FB_cursor_position() cx16.FB_cursor_position2()
cx16.FB_set_pixel(1) cx16.FB_set_pixel(1)
cx16.r0 = xcenter + yy cx16.r0 = xcenter + yy
cx16.r1 = ycenter - xx cx16.r1 = ycenter - xx
cx16.FB_cursor_position() cx16.FB_cursor_position2()
cx16.FB_set_pixel(1) cx16.FB_set_pixel(1)
cx16.r0 = xcenter - yy cx16.r0 = xcenter - yy
cx16.FB_cursor_position() cx16.FB_cursor_position2()
cx16.FB_set_pixel(1) cx16.FB_set_pixel(1)
yy++ yy++
if decisionOver2<=0 { if decisionOver2<=0 {
decisionOver2 += 2*yy+1 decisionOver2 += (yy as word)*2+1
} else { } else {
xx-- xx--
decisionOver2 += 2*(yy-xx)+1 decisionOver2 += (yy as word -xx)*2+1
} }
} }
} }
sub disc(uword xcenter, ubyte ycenter, ubyte radius) { sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; cx16.r0 = xcenter - radius/2 if radius==0
; cx16.r1 = ycenter - radius/2 return
; cx16.r2 = radius*2 ubyte @zp yy = 0
; cx16.r3 = radius*2 word decisionOver2 = (1 as word)-radius
; cx16.GRAPH_draw_oval(true) ; TODO currently is not implemented on cx16, does a BRK
ubyte xx = radius while radius>=yy {
ubyte yy = 0 horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
byte decisionOver2 = 1-xx as byte horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
while xx>=yy { horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
ubyte ycenter_plus_yy = ycenter + yy
ubyte ycenter_min_yy = ycenter - yy
ubyte ycenter_plus_xx = ycenter + xx
ubyte ycenter_min_xx = ycenter - xx
uword @zp plotx
for plotx in xcenter to xcenter+xx {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
for plotx in xcenter-xx to xcenter-1 {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_yy
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
for plotx in xcenter to xcenter+yy {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
for plotx in xcenter-yy to xcenter {
cx16.r0 = plotx
cx16.r1 = ycenter_plus_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
cx16.r1 = ycenter_min_xx
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
}
yy++ yy++
if decisionOver2<=0 if decisionOver2<=0
decisionOver2 += 2*yy+1 decisionOver2 += (yy as word)*2+1
else { else {
xx-- radius--
decisionOver2 += 2*(yy-xx)+1 decisionOver2 += (yy as word -radius)*2+1
} }
} }
} }
sub plot(uword plotx, ubyte ploty) { inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
cx16.r0 = plotx %asm {{
cx16.r1 = ploty jsr cx16.FB_cursor_position
cx16.FB_cursor_position() lda #1
cx16.FB_set_pixel(1) jsr cx16.FB_set_pixel
}}
} }
} }

View File

@ -0,0 +1,183 @@
%target cx16
; Manipulate the Commander X16's display color palette.
; Should you want to restore the default palette, you have to reinitialize the Vera yourself.
palette {
uword vera_palette_ptr
ubyte c
sub set_color(ubyte index, uword color) {
vera_palette_ptr = $fa00+index*2
cx16.vpoke(1, vera_palette_ptr, lsb(color))
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(color))
}
sub set_rgb4(uword palette_bytes_ptr, uword num_colors) {
; 2 bytes per color entry, the Vera uses this, but the R/GB bytes order is swapped
vera_palette_ptr = $fa00
repeat num_colors {
cx16.vpoke(1, vera_palette_ptr+1, @(palette_bytes_ptr))
palette_bytes_ptr++
cx16.vpoke(1, vera_palette_ptr, @(palette_bytes_ptr))
palette_bytes_ptr++
vera_palette_ptr+=2
}
}
sub set_rgb(uword palette_words_ptr, uword num_colors) {
; 1 word per color entry (in little endian format so $gb0r)
vera_palette_ptr = $fa00
repeat num_colors*2 {
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
palette_words_ptr++
vera_palette_ptr++
}
}
sub set_rgb8(uword palette_bytes_ptr, uword num_colors) {
; 3 bytes per color entry, adjust color depth from 8 to 4 bits per channel.
vera_palette_ptr = $fa00
ubyte red
ubyte greenblue
repeat num_colors {
red = @(palette_bytes_ptr) >> 4
palette_bytes_ptr++
greenblue = @(palette_bytes_ptr) & %11110000
palette_bytes_ptr++
greenblue |= @(palette_bytes_ptr) >> 4 ; add Blue
palette_bytes_ptr++
cx16.vpoke(1, vera_palette_ptr, greenblue)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, red)
vera_palette_ptr++
}
}
sub set_monochrome(uword screencolorRGB, uword drawcolorRGB) {
vera_palette_ptr = $fa00
cx16.vpoke(1, vera_palette_ptr, lsb(screencolorRGB)) ; G,B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(screencolorRGB)) ; R
vera_palette_ptr++
repeat 255 {
cx16.vpoke(1, vera_palette_ptr, lsb(drawcolorRGB)) ; G,B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(drawcolorRGB)) ; R
vera_palette_ptr++
}
}
sub set_grayscale() {
vera_palette_ptr = $fa00
repeat 16 {
c=0
repeat 16 {
cx16.vpoke(1, vera_palette_ptr, c)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, c)
vera_palette_ptr++
c += $11
}
}
}
uword[] C64_colorpalette_dark = [ ; this is a darker palette with more contrast
$000, ; 0 = black
$FFF, ; 1 = white
$632, ; 2 = red
$7AB, ; 3 = cyan
$638, ; 4 = purple
$584, ; 5 = green
$327, ; 6 = blue
$BC6, ; 7 = yellow
$642, ; 8 = orange
$430, ; 9 = brown
$965, ; 10 = light red
$444, ; 11 = dark grey
$666, ; 12 = medium grey
$9D8, ; 13 = light green
$65B, ; 14 = light blue
$999 ; 15 = light grey
]
uword[] C64_colorpalette_pepto = [ ; # this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
$000, ; 0 = black
$FFF, ; 1 = white
$833, ; 2 = red
$7cc, ; 3 = cyan
$839, ; 4 = purple
$5a4, ; 5 = green
$229, ; 6 = blue
$ef7, ; 7 = yellow
$852, ; 8 = orange
$530, ; 9 = brown
$c67, ; 10 = light red
$444, ; 11 = dark grey
$777, ; 12 = medium grey
$af9, ; 13 = light green
$76e, ; 14 = light blue
$bbb ; 15 = light grey
]
uword[] C64_colorpalette_light = [ ; this is a lighter palette
$000, ; 0 = black
$FFF, ; 1 = white
$944, ; 2 = red
$7CC, ; 3 = cyan
$95A, ; 4 = purple
$6A5, ; 5 = green
$549, ; 6 = blue
$CD8, ; 7 = yellow
$963, ; 8 = orange
$650, ; 9 = brown
$C77, ; 10 = light red
$666, ; 11 = dark grey
$888, ; 12 = medium grey
$AE9, ; 13 = light green
$87C, ; 14 = light blue
$AAA ; 15 = light grey
]
sub set_c64pepto() {
vera_palette_ptr = $fa00
repeat 16 {
for c in 0 to 15 {
uword cc = C64_colorpalette_pepto[c]
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
vera_palette_ptr++
}
}
}
sub set_c64light() {
vera_palette_ptr = $fa00
repeat 16 {
for c in 0 to 15 {
uword cc = C64_colorpalette_light[c]
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
vera_palette_ptr++
}
}
}
sub set_c64dark() {
vera_palette_ptr = $fa00
repeat 16 {
for c in 0 to 15 {
uword cc = C64_colorpalette_dark[c]
cx16.vpoke(1, vera_palette_ptr, lsb(cc)) ; G, B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(cc)) ; R
vera_palette_ptr++
}
}
}
}

View File

@ -24,7 +24,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; re
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer. NOTE: as a Cx16 extension, also returns the number of RAM memory banks in register A ! See MEMTOP2
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
@ -56,14 +56,57 @@ romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X. romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- utility
asmsub STOP2() -> ubyte @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the carry status flag.
%asm {{
phx
jsr c64.STOP
beq +
plx
lda #0
rts
+ plx
lda #1
rts
}}
}
asmsub RDTIM16() -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits for convenience
%asm {{
phx
jsr c64.RDTIM
pha
txa
tay
pla
plx
rts
}}
}
asmsub MEMTOP2() -> ubyte @A {
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks.
%asm {{
phx
sec
jsr c64.MEMTOP
plx
rts
}}
}
} }
cx16 { cx16 {
; 65c02 hardware vectors: ; irq and hardware vectors:
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in &uword CINV = $0314 ; IRQ vector (in ram)
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in &uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in &uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
; the sixteen virtual 16-bit registers ; the sixteen virtual 16-bit registers
@ -86,50 +129,50 @@ cx16 {
; VERA registers ; VERA registers
const uword VERA_BASE = $9F20 const uword VERA_BASE = $9F20
&ubyte VERA_ADDR_L = VERA_BASE + $0000 &ubyte VERA_ADDR_L = VERA_BASE + $0000
&ubyte VERA_ADDR_M = VERA_BASE + $0001 &ubyte VERA_ADDR_M = VERA_BASE + $0001
&ubyte VERA_ADDR_H = VERA_BASE + $0002 &ubyte VERA_ADDR_H = VERA_BASE + $0002
&ubyte VERA_DATA0 = VERA_BASE + $0003 &ubyte VERA_DATA0 = VERA_BASE + $0003
&ubyte VERA_DATA1 = VERA_BASE + $0004 &ubyte VERA_DATA1 = VERA_BASE + $0004
&ubyte VERA_CTRL = VERA_BASE + $0005 &ubyte VERA_CTRL = VERA_BASE + $0005
&ubyte VERA_IEN = VERA_BASE + $0006 &ubyte VERA_IEN = VERA_BASE + $0006
&ubyte VERA_ISR = VERA_BASE + $0007 &ubyte VERA_ISR = VERA_BASE + $0007
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008 &ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009 &ubyte VERA_DC_VIDEO = VERA_BASE + $0009
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A &ubyte VERA_DC_HSCALE = VERA_BASE + $000A
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B &ubyte VERA_DC_VSCALE = VERA_BASE + $000B
&ubyte VERA_DC_BORDER = VERA_BASE + $000C &ubyte VERA_DC_BORDER = VERA_BASE + $000C
&ubyte VERA_DC_HSTART = VERA_BASE + $0009 &ubyte VERA_DC_HSTART = VERA_BASE + $0009
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A &ubyte VERA_DC_HSTOP = VERA_BASE + $000A
&ubyte VERA_DC_VSTART = VERA_BASE + $000B &ubyte VERA_DC_VSTART = VERA_BASE + $000B
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C &ubyte VERA_DC_VSTOP = VERA_BASE + $000C
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D &ubyte VERA_L0_CONFIG = VERA_BASE + $000D
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E &ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F &ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010 &ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011 &ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012 &ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013 &ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014 &ubyte VERA_L1_CONFIG = VERA_BASE + $0014
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015 &ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016 &ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017 &ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018 &ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019 &ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A &ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B &ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C &ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D &ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
&ubyte VERA_SPI_DATA = VERA_BASE + $001E &ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F &ubyte VERA_SPI_CTRL = VERA_BASE + $001F
; VERA_PSG_BASE = $1F9C0 ; VERA_PSG_BASE = $1F9C0
; VERA_PALETTE_BASE = $1FA00 ; VERA_PALETTE_BASE = $1FA00
; VERA_SPRITES_BASE = $1FC00 ; VERA_SPRITES_BASE = $1FC00
; I/O ; I/O
const uword via1 = $9f60 ;VIA 6522 #1 const uword via1 = $9f60 ;VIA 6522 #1
&ubyte d1prb = via1+0 &ubyte d1prb = via1+0
&ubyte d1pra = via1+1 &ubyte d1pra = via1+1
&ubyte d1ddrb = via1+2 &ubyte d1ddrb = via1+2
@ -147,23 +190,23 @@ cx16 {
&ubyte d1ier = via1+14 &ubyte d1ier = via1+14
&ubyte d1ora = via1+15 &ubyte d1ora = via1+15
const uword via2 = $9f70 ;VIA 6522 #2 const uword via2 = $9f70 ;VIA 6522 #2
&ubyte d2prb =via2+0 &ubyte d2prb = via2+0
&ubyte d2pra =via2+1 &ubyte d2pra = via2+1
&ubyte d2ddrb =via2+2 &ubyte d2ddrb = via2+2
&ubyte d2ddra =via2+3 &ubyte d2ddra = via2+3
&ubyte d2t1l =via2+4 &ubyte d2t1l = via2+4
&ubyte d2t1h =via2+5 &ubyte d2t1h = via2+5
&ubyte d2t1ll =via2+6 &ubyte d2t1ll = via2+6
&ubyte d2t1lh =via2+7 &ubyte d2t1lh = via2+7
&ubyte d2t2l =via2+8 &ubyte d2t2l = via2+8
&ubyte d2t2h =via2+9 &ubyte d2t2h = via2+9
&ubyte d2sr =via2+10 &ubyte d2sr = via2+10
&ubyte d2acr =via2+11 &ubyte d2acr = via2+11
&ubyte d2pcr =via2+12 &ubyte d2pcr = via2+12
&ubyte d2ifr =via2+13 &ubyte d2ifr = via2+13
&ubyte d2ier =via2+14 &ubyte d2ier = via2+14
&ubyte d2ora =via2+15 &ubyte d2ora = via2+15
; ---- Commander X-16 additions on top of C64 kernal routines ---- ; ---- Commander X-16 additions on top of C64 kernal routines ----
@ -190,62 +233,228 @@ romsub $ff6b = mouse_get(ubyte zpdataptr @X) clobbers(A)
romsub $ff71 = mouse_scan() clobbers(A, X, Y) romsub $ff71 = mouse_scan() clobbers(A, X, Y)
romsub $ff53 = joystick_scan() clobbers(A, X, Y) romsub $ff53 = joystick_scan() clobbers(A, X, Y)
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff4d = clock_set_date_time() clobbers(A, X, Y) ; args: r0, r1, r2, r3L romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) ; outout args: r0, r1, r2, r3L romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered ; TODO specify the correct clobbers for alle these functions below, we now assume all 3 regs are clobbered
; high level graphics & fonts ; high level graphics & fonts
romsub $ff20 = GRAPH_init() clobbers(A,X,Y) ; uses vectors=r0 romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y) romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
romsub $ff26 = GRAPH_set_window() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3 romsub $ff26 = GRAPH_set_window(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y) romsub $ff29 = GRAPH_set_colors(ubyte stroke @A, ubyte fill @X, ubyte background @Y) clobbers (A,X,Y)
romsub $ff2c = GRAPH_draw_line() clobbers(A,X,Y) ; uses x1=r0, y1=r1, x2=r2, y2=r3 romsub $ff2c = GRAPH_draw_line(uword x1 @R0, uword y1 @R1, uword x2 @R2, uword y2 @R3) clobbers(A,X,Y)
romsub $ff2f = GRAPH_draw_rect(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3, cornerradius=r4 romsub $ff2f = GRAPH_draw_rect(uword x @R0, uword y @R1, uword width @R2, uword height @R3, uword cornerradius @R4, ubyte fill @Pc) clobbers(A,X,Y)
romsub $ff32 = GRAPH_move_rect() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, width=r4, height=r5 romsub $ff32 = GRAPH_move_rect(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword width @R4, uword height @R5) clobbers(A,X,Y)
romsub $ff35 = GRAPH_draw_oval(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3 romsub $ff35 = GRAPH_draw_oval(uword x @R0, uword y @R1, uword width @R2, uword height @R3, ubyte fill @Pc) clobbers(A,X,Y)
romsub $ff38 = GRAPH_draw_image() clobbers(A,X,Y) ; uses x=r0, y=r1, ptr=r2, width=r3, height=r4 romsub $ff38 = GRAPH_draw_image(uword x @R0, uword y @R1, uword ptr @R2, uword width @R3, uword height @R4) clobbers(A,X,Y)
romsub $ff3b = GRAPH_set_font() clobbers(A,X,Y) ; uses ptr=r0 romsub $ff3b = GRAPH_set_font(uword fontptr @R0) clobbers(A,X,Y)
romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc) clobbers(A,X,Y) romsub $ff3e = GRAPH_get_char_size(ubyte baseline @A, ubyte width @X, ubyte height_or_style @Y, ubyte is_control @Pc) clobbers(A,X,Y)
romsub $ff41 = GRAPH_put_char(ubyte char @A) clobbers(A,X,Y) ; uses x=r0, y=r1 romsub $ff41 = GRAPH_put_char(uword x @R0, uword y @R1, ubyte char @A) clobbers(A,X,Y)
romsub $ff41 = GRAPH_put_next_char(ubyte char @A) clobbers(A,X,Y) ; alias for the routine above that doesn't reset the position of the initial character
; framebuffer ; framebuffer
romsub $fef6 = FB_init() clobbers(A,X,Y) romsub $fef6 = FB_init() clobbers(A,X,Y)
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A ; also outputs width=r0, height=r1 romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
romsub $fefc = FB_set_palette(ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y) ; also uses pointer=r0 romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y)
romsub $feff = FB_cursor_position() clobbers(A,X,Y) ; uses x=r0, y=r1 romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
romsub $ff02 = FB_cursor_next_line() clobbers(A,X,Y) ; uses x=r0 romsub $feff = FB_cursor_position2() clobbers(A,X,Y) ; alias for the previous routine, but avoiding having to respecify both x and y every time
romsub $ff02 = FB_cursor_next_line(uword x @R0) clobbers(A,X,Y)
romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A romsub $ff05 = FB_get_pixel() clobbers(X,Y) -> ubyte @A
romsub $ff08 = FB_get_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1 romsub $ff08 = FB_get_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y) romsub $ff0b = FB_set_pixel(ubyte color @A) clobbers(A,X,Y)
romsub $ff0e = FB_set_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1 romsub $ff0e = FB_set_pixels(uword pointer @R0, uword count @R1) clobbers(A,X,Y)
romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y) romsub $ff11 = FB_set_8_pixels(ubyte pattern @A, ubyte color @X) clobbers(A,X,Y)
romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y) ; also uses mask=r0L romsub $ff14 = FB_set_8_pixels_opaque(ubyte pattern @R0, ubyte mask @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y)
romsub $ff17 = FB_fill_pixels(ubyte color @A) clobbers(A,X,Y) ; also uses count=r0, step=r1 romsub $ff17 = FB_fill_pixels(uword count @R0, uword pstep @R1, ubyte color @A) clobbers(A,X,Y)
romsub $ff1a = FB_filter_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1 romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1) clobbers(A,X,Y)
romsub $ff1d = FB_move_pixels() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, count=r4 romsub $ff1d = FB_move_pixels(uword sx @R0, uword sy @R1, uword tx @R2, uword ty @R3, uword count @R4) clobbers(A,X,Y)
; misc ; misc
romsub $fef0 = sprite_set_image(ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc ; also uses pixels=r0, mask=r1, bpp=r2L romsub $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc
romsub $fef3 = sprite_set_position(ubyte number @A) clobbers(A,X,Y) ; also uses x=r0 and y=r1 romsub $fef3 = sprite_set_position(uword x @R0, uword y @R1, ubyte number @A) clobbers(A,X,Y)
romsub $fee4 = memory_fill(ubyte value @A) clobbers(A,X,Y) ; uses address=r0, num_bytes=r1 romsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A) clobbers(A,X,Y)
romsub $fee7 = memory_copy() clobbers(A,X,Y) ; uses source=r0, target=r1, num_bytes=r2 romsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @R2) clobbers(A,X,Y)
romsub $feea = memory_crc() clobbers(A,X,Y) ; uses address=r0, num_bytes=r1 result->r2 romsub $feea = memory_crc(uword address @R0, uword num_bytes @R1) clobbers(A,X,Y) -> uword @R2
romsub $feed = memory_decompress() clobbers(A,X,Y) ; uses input=r0, output=r1 result->r1 romsub $feed = memory_decompress(uword input @R0, uword output @R1) clobbers(A,X,Y) -> uword @R1 ; last address +1 is result in R1
romsub $fedb = console_init() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3 romsub $fedb = console_init(uword x @R0, uword y @R1, uword width @R2, uword height @R3) clobbers(A,X,Y)
romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc) clobbers(A,X,Y) romsub $fede = console_put_char(ubyte char @A, ubyte wrapping @Pc) clobbers(A,X,Y)
romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
romsub $fed8 = console_put_image() clobbers(A,X,Y) ; uses ptr=r0, width=r1, height=r2 romsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2) clobbers(A,X,Y)
romsub $fed5 = console_set_paging_message() clobbers(A,X,Y) ; uses messageptr=r0 romsub $fed5 = console_set_paging_message(uword msgptr @R0) clobbers(A,X,Y)
romsub $fed2 = kbdbuf_put(ubyte key @A) clobbers(A,X,Y) romsub $fed2 = kbdbuf_put(ubyte key @A) clobbers(A,X,Y)
romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y romsub $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
romsub $fecc = monitor() clobbers(A,X,Y) romsub $fecc = monitor() clobbers(A,X,Y)
; ---- end of kernal routines ---- ; ---- end of kernal routines ----
asmsub init_system() {
; ---- utilities -----
inline asmsub rombank(ubyte rombank @A) {
; -- set the rom banks
%asm {{
sta $01 ; rom bank register (new)
sta cx16.d1prb ; rom bank register (old)
}}
}
inline asmsub rambank(ubyte rambank @A) {
; -- set the ram bank
%asm {{
sta $00 ; ram bank register (new)
sta cx16.d1pra ; ram bank register (old)
}}
}
asmsub vpeek(ubyte bank @A, uword address @XY) -> ubyte @A {
; -- get a byte from VERA's video memory
; note: inefficient when reading multiple sequential bytes!
%asm {{
pha
lda #1
sta cx16.VERA_CTRL
pla
and #1
sta cx16.VERA_ADDR_H
sty cx16.VERA_ADDR_M
stx cx16.VERA_ADDR_L
lda cx16.VERA_DATA1
rts
}}
}
asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrOrDecrByOne @Y) clobbers(A) {
; -- setup the VERA's data address register 0 or 1
%asm {{
and #1
pha
lda cx16.r1
and #1
sta cx16.VERA_CTRL
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
pla
cpy #0
bmi ++
beq +
ora #%00010000
+ sta cx16.VERA_ADDR_H
rts
+ ora #%00011000
sta cx16.VERA_ADDR_H
rts
}}
}
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
; -- write a single byte to VERA's video memory
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
sty cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_or(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
; -- or a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
ora cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_and(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
; -- and a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
and cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
; -- xor a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
sub FB_set_pixels_from_buf(uword buffer, uword count) {
%asm {{
; -- This is replacement code for the normal FB_set_pixels subroutine in ROM
; However that routine contains a bug in the current v38 ROM that makes it crash when count > 255.
; So the code below replaces that. Once the ROM is patched this routine is no longer necessary.
; See https://github.com/commanderx16/x16-rom/issues/179
phx
lda buffer
ldy buffer+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
jsr _pixels
plx
rts
_pixels lda count+1
beq +
ldx #0
- jsr _loop
inc P8ZP_SCRATCH_W1+1
dec count+1
bne -
+ ldx count
_loop ldy #0
- lda (P8ZP_SCRATCH_W1),y
sta cx16.VERA_DATA0
iny
dex
bne -
rts
}}
}
; ---- system stuff -----
asmsub init_system() {
; Initializes the machine to a sane starting state. ; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic. ; Called automatically by the loader program logic.
%asm {{ %asm {{
@ -253,7 +462,7 @@ asmsub init_system() {
cld cld
;stz $00 ;stz $00
;stz $01 ;stz $01
;stz d1prb ; select rom bank 0 ;stz d1prb ; select rom bank 0 (enable kernal)
lda #$80 lda #$80
sta VERA_CTRL sta VERA_CTRL
jsr c64.IOINIT jsr c64.IOINIT
@ -277,15 +486,297 @@ asmsub init_system() {
}} }}
} }
asmsub reset_system() { asmsub init_system_phase2() {
; Soft-reset the system back to Basic prompt.
%asm {{ %asm {{
sei sei
lda #14 lda cx16.CINV
sta $01 sta restore_irq._orig_irqvec
stz cx16.d1prb ; bank the kernal in lda cx16.CINV+1
jmp (cx16.RESET_VEC) sta restore_irq._orig_irqvec+1
cli
rts
}}
}
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei
lda #<_irq_handler
sta cx16.CINV
lda #>_irq_handler
sta cx16.CINV+1
lda cx16.VERA_IEN
ora #%00000001 ; enable the vsync irq
sta cx16.VERA_IEN
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #1
sta cx16.VERA_ISR ; clear Vera Vsync irq status
ply
plx
pla
rti
+ jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
stx IRQ_X_REG
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
; stack protector; make sure we don't clobber the top of the evaluation stack
dex
dex
dex
dex
dex
dex
cld
rts
_irq_handler_end
; restore all zp scratch registers and the X register
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
ldx IRQ_X_REG
rts
IRQ_X_REG .byte 0
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda _orig_irqvec
sta cx16.CINV
lda _orig_irqvec+1
sta cx16.CINV+1
lda cx16.VERA_IEN
and #%11110000 ; disable all Vera IRQs
ora #%00000001 ; enable only the vsync Irq
sta cx16.VERA_IEN
cli
rts
_orig_irqvec .word 0
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
sei
lda cx16.VERA_IEN
and #%11110000 ; clear other IRQs
ora #%00000010 ; enable the line (raster) irq
sta cx16.VERA_IEN
lda cx16.r0
ldy cx16.r0+1
jsr set_rasterline
lda #<_raster_irq_handler
sta cx16.CINV
lda #>_raster_irq_handler
sta cx16.CINV+1
cli
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #%00000010
sta cx16.VERA_ISR ; clear Vera line irq status
ply
plx
pla
rti
}}
}
asmsub set_rasterline(uword line @AY) {
%asm {{
sta cx16.VERA_IRQ_LINE_L
lda cx16.VERA_IEN
and #%01111111
sta cx16.VERA_IEN
tya
lsr a
ror a
and #%10000000
ora cx16.VERA_IEN
sta cx16.VERA_IEN
rts
}} }}
} }
} }
sys {
; ------- lowlevel system routines --------
const ubyte target = 16 ; compilation target specifier. 64 = C64, 16 = CommanderX16.
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
%asm {{
sei
stz $01 ; bank the kernal in (new rom bank register)
stz cx16.d1prb ; bank the kernal in (old rom bank register)
jmp (cx16.RESET_VEC)
}}
}
sub wait(uword jiffies) {
; --- wait approximately the given number of jiffies (1/60th seconds)
repeat jiffies {
ubyte jiff = lsb(c64.RDTIM16())
while jiff==lsb(c64.RDTIM16()) {
; wait until 1 jiffy has passed
}
}
}
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
%asm {{
sta cx16.r2
sty cx16.r2+1
jsr cx16.memory_copy
}}
}
inline asmsub memset(uword mem @R0, uword numbytes @R1, ubyte value @A) clobbers(A,X,Y) {
%asm {{
jsr cx16.memory_fill
}}
}
asmsub memsetw(uword mem @R0, uword numwords @R1, uword value @AY) clobbers (A,X,Y) {
%asm {{
ldx cx16.r0
stx P8ZP_SCRATCH_W1
ldx cx16.r0+1
stx P8ZP_SCRATCH_W1+1
ldx cx16.r1
stx P8ZP_SCRATCH_W2
ldx cx16.r1+1
stx P8ZP_SCRATCH_W2+1
jmp prog8_lib.memsetw
}}
}
inline asmsub rsave() {
; save cpu status flag and all registers A, X, Y.
; see http://6502.org/tutorials/register_preservation.html
%asm {{
php
pha
phy
phx
}}
}
inline asmsub rrestore() {
; restore all registers and cpu status flag
%asm {{
plx
ply
pla
plp
}}
}
inline asmsub read_flags() -> ubyte @A {
%asm {{
php
pla
}}
}
inline asmsub clear_carry() {
%asm {{
clc
}}
}
inline asmsub set_carry() {
%asm {{
sec
}}
}
inline asmsub clear_irqd() {
%asm {{
cli
}}
}
inline asmsub set_irqd() {
%asm {{
sei
}}
}
inline asmsub exit(ubyte returnvalue @A) {
; -- immediately exit the program with a return code in the A register
%asm {{
jsr c64.CLRCHN ; reset i/o channels
ldx prog8_lib.orig_stackpointer
txs
rts ; return to original caller
}}
}
inline asmsub progend() -> uword @AY {
%asm {{
lda #<prog8_program_end
ldy #>prog8_program_end
}}
}
}

View File

@ -15,8 +15,31 @@ const ubyte DEFAULT_WIDTH = 80
const ubyte DEFAULT_HEIGHT = 60 const ubyte DEFAULT_HEIGHT = 60
sub clear_screen() { sub clear_screen() {
clear_screenchars(' ') txt.chrout(147)
}
sub home() {
txt.chrout(19)
}
sub nl() {
txt.chrout('\n')
}
sub spc() {
txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
sec
jsr c64.PLOT
tay
clc
jmp c64.PLOT
}}
} }
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) { asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
@ -154,10 +177,9 @@ sub uppercase() {
cx16.screen_set_charset(2, 0) ; uppercase charset cx16.screen_set_charset(2, 0) ; uppercase charset
} }
asmsub scroll_left (ubyte dummy @ Pc) clobbers(A, Y) { asmsub scroll_left() clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left ; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself ; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag is a dummy on the cx16
%asm {{ %asm {{
phx phx
jsr c64.SCREEN jsr c64.SCREEN
@ -198,10 +220,9 @@ _lx ldx #0 ; modified
}} }}
} }
asmsub scroll_right (ubyte dummy @ Pc) clobbers(A) { asmsub scroll_right() clobbers(A) {
; ---- scroll the whole screen 1 character to the right ; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself ; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag is a dummy on the cx16
%asm {{ %asm {{
phx phx
jsr c64.SCREEN jsr c64.SCREEN
@ -250,10 +271,9 @@ _lx ldx #0 ; modified
}} }}
} }
asmsub scroll_up (ubyte dummy @ Pc) clobbers(A, Y) { asmsub scroll_up() clobbers(A, Y) {
; ---- scroll the whole screen 1 character up ; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself ; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag is a dummy on the cx16
%asm {{ %asm {{
phx phx
jsr c64.SCREEN jsr c64.SCREEN
@ -300,10 +320,9 @@ _nextline
}} }}
} }
asmsub scroll_down (ubyte dummy @ Pc) clobbers(A, Y) { asmsub scroll_down() clobbers(A, Y) {
; ---- scroll the whole screen 1 character down ; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself ; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag is a dummy on the cx16
%asm {{ %asm {{
phx phx
jsr c64.SCREEN jsr c64.SCREEN
@ -401,7 +420,7 @@ _print_byte_digits
jsr c64.CHROUT jsr c64.CHROUT
pla pla
jsr c64.CHROUT jsr c64.CHROUT
jmp _ones bra _ones
+ pla + pla
cmp #'0' cmp #'0'
beq _ones beq _ones
@ -424,7 +443,7 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
jsr c64.CHROUT jsr c64.CHROUT
+ pla + pla
jsr conv.byte2decimal jsr conv.byte2decimal
jmp print_ub._print_byte_digits bra print_ub._print_byte_digits
}} }}
} }
@ -475,7 +494,7 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
jsr print_ubbin jsr print_ubbin
pla pla
clc clc
jmp print_ubbin bra print_ubbin
}} }}
} }
@ -488,7 +507,7 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
jsr print_ubhex jsr print_ubhex
pla pla
clc clc
jmp print_ubhex bra print_ubhex
}} }}
} }
@ -551,7 +570,7 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
adc #1 adc #1
bcc + bcc +
iny iny
+ jmp print_uw + bra print_uw
}} }}
} }
@ -579,95 +598,126 @@ asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) { asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
; ---- sets the character in the screen matrix at the given position ; ---- sets the character in the screen matrix at the given position
%asm {{ %asm {{
pha pha
txa txa
asl a asl a
stz cx16.VERA_CTRL stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M sty cx16.VERA_ADDR_M
pla pla
sta cx16.VERA_DATA0 sta cx16.VERA_DATA0
rts rts
}} }}
} }
asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A { asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location ; ---- get the character in the screen matrix at the given location
%asm {{ %asm {{
asl a asl a
stz cx16.VERA_CTRL stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0 lda cx16.VERA_DATA0
rts rts
}} }}
} }
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) { asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
; ---- set the color in A on the screen matrix at the given position ; ---- set the color in A on the screen matrix at the given position
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color!
%asm {{ %asm {{
pha pha
txa txa
asl a asl a
ina ina
stz cx16.VERA_CTRL stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M sty cx16.VERA_ADDR_M
pla pla
sta cx16.VERA_DATA0 sta cx16.VERA_DATA0
rts rts
}} }}
} }
asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A { asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- get the color in the screen color matrix at the given location ; ---- get the color in the screen color matrix at the given location
%asm {{ %asm {{
asl a asl a
ina ina
stz cx16.VERA_CTRL stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0 lda cx16.VERA_DATA0
rts rts
}} }}
} }
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) { sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen ; ---- set char+color at the given position on the screen
; note: color handling is the same as on the C64: it only sets the foreground color.
; use setcc2 if you want Cx-16 specific feature of setting both Bg+Fg colors.
%asm {{ %asm {{
phx phx
lda column lda column
asl a asl a
tax tax
ldy row ldy row
lda charcolor lda charcolor
and #$0f and #$0f
sta P8ZP_SCRATCH_B1 sta P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M sty cx16.VERA_ADDR_M
lda char lda char
sta cx16.VERA_DATA0 sta cx16.VERA_DATA0
inx inx
stz cx16.VERA_ADDR_H stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0 lda cx16.VERA_DATA0
and #$f0 and #$f0
ora P8ZP_SCRATCH_B1 ora P8ZP_SCRATCH_B1
sta cx16.VERA_DATA0 sta cx16.VERA_DATA0
plx plx
rts rts
}}
}
sub setcc2 (ubyte column, ubyte row, ubyte char, ubyte colors) {
; ---- set char+color at the given position on the screen
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color!
%asm {{
phx
lda column
asl a
tax
ldy row
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda colors
sta cx16.VERA_DATA0
plx
rts
}} }}
} }
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) { asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register. ; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{ %asm {{
phx phx
tax tax

View File

@ -1,3 +1,5 @@
; routine to draw the Commander X16's log in petscii.
%import textio %import textio
cx16logo { cx16logo {
@ -14,7 +16,7 @@ cx16logo {
uword strptr uword strptr
for strptr in logo_lines for strptr in logo_lines
txt.print(strptr) txt.print(strptr)
txt.chrout('\n') txt.nl()
} }
str[] logo_lines = [ str[] logo_lines = [

View File

@ -1,20 +1,22 @@
%import textio ; C64 and Cx16 disk drive I/O routines.
%import syslib ;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
; Note: this code is compatible with C64 and CX16. %import textio
%import string
%import syslib
diskio { diskio {
sub directory(ubyte drivenumber) -> ubyte {
sub directory(ubyte drivenumber) -> byte { ; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
; -- Shows the directory contents of disk drive 8-11 (provide as argument).
c64.SETNAM(1, "$") c64.SETNAM(1, "$")
c64.SETLFS(1, drivenumber, 0) c64.SETLFS(13, drivenumber, 0)
void c64.OPEN() ; open 1,8,0,"$" void c64.OPEN() ; open 13,8,0,"$"
if_cs if_cs
goto io_error goto io_error
void c64.CHKIN(1) ; use #1 as input channel void c64.CHKIN(13) ; use #13 as input channel
if_cs if_cs
goto io_error goto io_error
@ -25,31 +27,34 @@ diskio {
; while not key pressed / EOF encountered, read data. ; while not key pressed / EOF encountered, read data.
ubyte status = c64.READST() ubyte status = c64.READST()
while not status { while not status {
txt.print_uw(mkword(c64.CHRIN(), c64.CHRIN())) ubyte low = c64.CHRIN()
txt.chrout(' ') ubyte high = c64.CHRIN()
txt.print_uw(mkword(high, low))
txt.spc()
ubyte @zp char ubyte @zp char
do { repeat {
char = c64.CHRIN() char = c64.CHRIN()
if char==0
break
txt.chrout(char) txt.chrout(char)
} until char==0 }
txt.chrout('\n') txt.nl()
void c64.CHRIN() ; skip 2 bytes void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN() void c64.CHRIN()
status = c64.READST() status = c64.READST()
void c64.STOP() if c64.STOP2()
if_nz
break break
} }
io_error: io_error:
status = c64.READST() status = c64.READST()
c64.CLRCHN() ; restore default i/o devices c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(1) c64.CLOSE(13)
if status and status != 64 { ; 64=end of file if status and status & $40 == 0 { ; bit 6=end of file
txt.print("\ni/o error, status: ") txt.print("\ni/o error, status: ")
txt.print_ub(status) txt.print_ub(status)
txt.chrout('\n') txt.nl()
return false return false
} }
@ -57,9 +62,284 @@ io_error:
} }
sub status(ubyte drivenumber) { ; internal variables for the iterative file lister / loader
; -- display the disk drive's current status message ubyte list_skip_disk_name
c64.SETNAM(0, $0000) uword list_pattern
uword list_blocks
ubyte iteration_in_progress = false
ubyte @zp first_byte
ubyte have_first_byte
str list_filename = "?" * 32
; ----- get a list of files (uses iteration functions internally) -----
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested.
uword names_buffer = memory("filenames", 512)
uword buffer_start = names_buffer
ubyte files_found = 0
if lf_start_list(drivenumber, pattern_ptr) {
while lf_next_entry() {
@(name_ptrs) = lsb(names_buffer)
name_ptrs++
@(name_ptrs) = msb(names_buffer)
name_ptrs++
names_buffer += string.copy(diskio.list_filename, names_buffer) + 1
files_found++
if names_buffer - buffer_start > 512-18
break
if files_found == max_names
break
}
lf_end_list()
}
return files_found
}
; ----- iterative file lister functions (uses io channel 12) -----
sub lf_start_list(ubyte drivenumber, uword pattern_ptr) -> ubyte {
; -- start an iterative file listing with optional pattern matching.
; note: only a single iteration loop can be active at a time!
lf_end_list()
list_pattern = pattern_ptr
list_skip_disk_name = true
iteration_in_progress = true
c64.SETNAM(1, "$")
c64.SETLFS(12, drivenumber, 0)
void c64.OPEN() ; open 12,8,0,"$"
if_cs
goto io_error
void c64.CHKIN(12) ; use #12 as input channel
if_cs
goto io_error
repeat 4 {
void c64.CHRIN() ; skip the 4 prologue bytes
}
if c64.READST()==0
return true
io_error:
lf_end_list()
return false
}
sub lf_next_entry() -> ubyte {
; -- retrieve the next entry from an iterative file listing session.
; results will be found in list_blocks and list_filename.
; if it returns false though, there are no more entries (or an error occurred).
if not iteration_in_progress
return false
repeat {
void c64.CHKIN(12) ; use #12 as input channel again
uword nameptr = &list_filename
ubyte blocks_lsb = c64.CHRIN()
ubyte blocks_msb = c64.CHRIN()
if c64.READST()
goto close_end
list_blocks = mkword(blocks_msb, blocks_lsb)
; read until the filename starts after the first "
while c64.CHRIN()!='\"' {
if c64.READST()
goto close_end
}
; read the filename
repeat {
ubyte char = c64.CHRIN()
if char==0
break
if char=='\"'
break
@(nameptr) = char
nameptr++
}
@(nameptr) = 0
while c64.CHRIN() {
; read the rest of the entry until the end
}
void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN()
if not list_skip_disk_name {
if not list_pattern
return true
if prog8_lib.pattern_match(list_filename, list_pattern)
return true
}
list_skip_disk_name = false
}
close_end:
lf_end_list()
return false
}
sub lf_end_list() {
; -- end an iterative file listing session (close channels).
if iteration_in_progress {
c64.CLRCHN()
c64.CLOSE(12)
iteration_in_progress = false
}
}
; ----- iterative file loader functions (uses io channel 11) -----
sub f_open(ubyte drivenumber, uword filenameptr) -> ubyte {
; -- open a file for iterative reading with f_read
; note: only a single iteration loop can be active at a time!
f_close()
c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(11, drivenumber, 0)
void c64.OPEN() ; open 11,8,0,"filename"
if_cc {
iteration_in_progress = true
have_first_byte = false
void c64.CHKIN(11) ; use #11 as input channel
if_cc {
first_byte = c64.CHRIN() ; read first byte to test for file not found
if not c64.READST() {
have_first_byte = true
return true
}
}
}
f_close()
return false
}
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
; -- read from the currently open file, up to the given number of bytes.
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
if not iteration_in_progress or not num_bytes
return 0
list_blocks = 0 ; we reuse this variable for the total number of bytes read
if have_first_byte {
have_first_byte=false
@(bufferpointer) = first_byte
bufferpointer++
list_blocks++
num_bytes--
}
void c64.CHKIN(11) ; use #11 as input channel again
%asm {{
lda bufferpointer
sta _in_buffer+1
lda bufferpointer+1
sta _in_buffer+2
}}
repeat num_bytes {
%asm {{
jsr c64.CHRIN
sta cx16.r5
_in_buffer sta $ffff
inc _in_buffer+1
bne +
inc _in_buffer+2
+ inc list_blocks
bne +
inc list_blocks+1
+
}}
if cx16.r5==$0d { ; chance on I/o error status?
first_byte = c64.READST()
if first_byte & $40
f_close() ; end of file, close it
if first_byte
return list_blocks
}
}
return list_blocks
}
sub f_read_all(uword bufferpointer) -> uword {
; -- read the full contents of the file, returns number of bytes read.
if not iteration_in_progress
return 0
list_blocks = 0 ; we reuse this variable for the total number of bytes read
if have_first_byte {
have_first_byte=false
@(bufferpointer) = first_byte
bufferpointer++
list_blocks++
}
while not c64.READST() {
list_blocks += f_read(bufferpointer, 256)
bufferpointer += 256
}
return list_blocks
}
asmsub f_readline(uword bufptr @AY) clobbers(X) -> ubyte @Y {
; Routine to read text lines from a text file. Lines must be less than 255 characters.
; Reads characters from the input file UNTIL a newline or return character (or EOF).
; The line read will be 0-terminated in the buffer (and not contain the end of line character).
; The length of the line is returned in Y. Note that an empty line is okay and is length 0!
; I/O error status should be checked by the caller itself via READST() routine.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldx #11
jsr c64.CHKIN ; use channel 11 again for input
ldy #0
lda have_first_byte
beq _loop
lda #0
sta have_first_byte
lda first_byte
sta (P8ZP_SCRATCH_W1),y
iny
_loop jsr c64.CHRIN
sta (P8ZP_SCRATCH_W1),y
beq _end
iny
cmp #$0a
beq _line_end
cmp #$0d
bne _loop
_line_end dey ; get rid of the trailing end-of-line char
lda #0
sta (P8ZP_SCRATCH_W1),y
_end rts
}}
}
sub f_close() {
; -- end an iterative file loading session (close channels).
if iteration_in_progress {
c64.CLRCHN()
c64.CLOSE(11)
iteration_in_progress = false
}
}
sub status(ubyte drivenumber) -> uword {
; -- retrieve the disk drive's current status message
uword messageptr = &filename
c64.SETNAM(0, filename)
c64.SETLFS(15, drivenumber, 15) c64.SETLFS(15, drivenumber, 15)
void c64.OPEN() ; open 15,8,15 void c64.OPEN() ; open 15,8,15
if_cs if_cs
@ -68,17 +348,21 @@ io_error:
if_cs if_cs
goto io_error goto io_error
while not c64.READST() while not c64.READST() {
txt.chrout(c64.CHRIN()) @(messageptr) = c64.CHRIN()
messageptr++
}
io_error: io_error:
@(messageptr) = 0
c64.CLRCHN() ; restore default i/o devices c64.CLRCHN() ; restore default i/o devices
c64.CLOSE(15) c64.CLOSE(15)
return filename
} }
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> byte { sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
c64.SETNAM(strlen(filenameptr), filenameptr) c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0) c64.SETLFS(1, drivenumber, 0)
uword end_address = address + size uword end_address = address + size
@ -97,14 +381,18 @@ io_error:
plp plp
}} }}
first_byte = 0 ; result var reuse
if_cc if_cc
return c64.READST()==0 first_byte = c64.READST()==0
return false c64.CLRCHN()
c64.CLOSE(1)
return first_byte
} }
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword { sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
c64.SETNAM(strlen(filenameptr), filenameptr) c64.SETNAM(string.length(filenameptr), filenameptr)
ubyte secondary = 1 ubyte secondary = 1
uword end_of_load = 0 uword end_of_load = 0
if address_override if address_override
@ -122,6 +410,9 @@ io_error:
+ ldx P8ZP_SCRATCH_REG + ldx P8ZP_SCRATCH_REG
}} }}
c64.CLRCHN()
c64.CLOSE(1)
if end_of_load if end_of_load
return end_of_load - address_override return end_of_load - address_override
@ -133,10 +424,9 @@ io_error:
sub delete(ubyte drivenumber, uword filenameptr) { sub delete(ubyte drivenumber, uword filenameptr) {
; -- delete a file on the drive ; -- delete a file on the drive
ubyte flen = strlen(filenameptr)
filename[0] = 's' filename[0] = 's'
filename[1] = ':' filename[1] = ':'
memcopy(filenameptr, &filename+2, flen+1) ubyte flen = string.copy(filenameptr, &filename+2)
c64.SETNAM(flen+2, filename) c64.SETNAM(flen+2, filename)
c64.SETLFS(1, drivenumber, 15) c64.SETLFS(1, drivenumber, 15)
void c64.OPEN() void c64.OPEN()
@ -146,14 +436,11 @@ io_error:
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) { sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
; -- rename a file on the drive ; -- rename a file on the drive
ubyte flen_old = strlen(oldfileptr)
ubyte flen_new = strlen(newfileptr)
filename[0] = 'r' filename[0] = 'r'
filename[1] = ':' filename[1] = ':'
memcopy(newfileptr, &filename+2, flen_new) ubyte flen_new = string.copy(newfileptr, &filename+2)
ubyte fis_ix = flen_new+2 ; TODO is temp var for array indexing filename[flen_new+2] = '='
filename[fis_ix] = '=' ubyte flen_old = string.copy(oldfileptr, &filename+3+flen_new)
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
c64.SETNAM(3+flen_new+flen_old, filename) c64.SETNAM(3+flen_new+flen_old, filename)
c64.SETLFS(1, drivenumber, 15) c64.SETLFS(1, drivenumber, 15)
void c64.OPEN() void c64.OPEN()

View File

@ -1,11 +1,8 @@
; Prog8 internal Math library routines - always included by the compiler ; Internal Math library routines - always included by the compiler
; Generic machine independent 6502 code. ; Generic machine independent 6502 code.
; ;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
; ;
; indent format: TABS, size=8
; some more interesting routines can be found here: ; some more interesting routines can be found here:
; http://6502org.wikidot.com/software-math ; http://6502org.wikidot.com/software-math
; http://codebase64.org/doku.php?id=base:6502_6510_maths ; http://codebase64.org/doku.php?id=base:6502_6510_maths
@ -247,8 +244,8 @@ randseed .proc
.pend .pend
randbyte .proc fast_randbyte .proc
; -- 8-bit pseudo random number generator into A ; -- fast but bad 8-bit pseudo random number generator into A
lda _seed lda _seed
beq _eor beq _eor
asl a asl a
@ -266,6 +263,10 @@ _seed .byte $3a
.pend .pend
randbyte .proc
; -- 8 bit pseudo random number generator into A (by just reusing randword)
jmp randword
.pend
randword .proc randword .proc
; -- 16 bit pseudo random number generator into AY ; -- 16 bit pseudo random number generator into AY
@ -774,6 +775,31 @@ stack_mul_word_100 .proc
rts rts
.pend .pend
stack_mul_word_320 .proc
; stackW = stackLo * 256 + stackLo * 64 (stackHi doesn't matter)
ldy P8ESTACK_LO+1,x
lda #0
sta P8ESTACK_HI+1,x
tya
asl a
rol P8ESTACK_HI+1,x
asl a
rol P8ESTACK_HI+1,x
asl a
rol P8ESTACK_HI+1,x
asl a
rol P8ESTACK_HI+1,x
asl a
rol P8ESTACK_HI+1,x
asl a
rol P8ESTACK_HI+1,x
sta P8ESTACK_LO+1,x
tya
clc
adc P8ESTACK_HI+1,x
sta P8ESTACK_HI+1,x
rts
.pend
; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : --------- ; ----------- optimized multiplications (in-place A (byte) and ?? (word)) : ---------
mul_byte_3 .proc mul_byte_3 .proc
@ -1178,8 +1204,6 @@ mul_word_40 .proc
rol a rol a
asl P8ZP_SCRATCH_W1 asl P8ZP_SCRATCH_W1
rol a rol a
asl P8ZP_SCRATCH_W1
rol a
tay tay
lda P8ZP_SCRATCH_W1 lda P8ZP_SCRATCH_W1
rts rts
@ -1241,48 +1265,35 @@ mul_word_100 .proc
rts rts
.pend .pend
mul_word_320 .proc
; AY = A * 256 + A * 64 (msb in Y doesn't matter)
sta P8ZP_SCRATCH_B1
ldy #0
sty P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
pha
clc
lda P8ZP_SCRATCH_B1
adc P8ZP_SCRATCH_REG
tay
pla
rts
.pend
; ----------- end optimized multiplications ----------- ; ----------- end optimized multiplications -----------
sign_b .proc
lda P8ESTACK_LO+1,x
beq _sign_zero
bmi _sign_neg
_sign_pos lda #1
sta P8ESTACK_LO+1,x
rts
_sign_neg lda #-1
_sign_zero sta P8ESTACK_LO+1,x
rts
.pend
sign_ub .proc
lda P8ESTACK_LO+1,x
beq sign_b._sign_zero
bne sign_b._sign_pos
.pend
sign_w .proc
lda P8ESTACK_HI+1,x
bmi sign_b._sign_neg
beq sign_ub
bne sign_b._sign_pos
.pend
sign_uw .proc
lda P8ESTACK_HI+1,x
beq _sign_possibly_zero
_sign_pos lda #1
sta P8ESTACK_LO+1,x
rts
_sign_possibly_zero lda P8ESTACK_LO+1,x
bne _sign_pos
sta P8ESTACK_LO+1,x
rts
.pend
; bit shifts. ; bit shifts.
; anything below 3 is done inline. anything above 7 is done via other optimizations. ; anything below 3 is done inline. anything above 7 is done via other optimizations.
@ -1340,6 +1351,33 @@ shift_left_w_3 .proc
jmp shift_left_w_7._shift3 jmp shift_left_w_7._shift3
.pend .pend
shift_left_w .proc
; -- variable number of shifts left
inx
ldy P8ESTACK_LO,x
bne _shift
rts
_shift asl P8ESTACK_LO+1,x
rol P8ESTACK_HI+1,x
dey
bne _shift
rts
.pend
shift_right_uw .proc
; -- uword variable number of shifts right
inx
ldy P8ESTACK_LO,x
bne _shift
rts
_shift lsr P8ESTACK_HI+1,x
ror P8ESTACK_LO+1,x
dey
bne _shift
rts
.pend
shift_right_uw_7 .proc shift_right_uw_7 .proc
lda P8ESTACK_LO+1,x lda P8ESTACK_LO+1,x
sta P8ZP_SCRATCH_B1 sta P8ZP_SCRATCH_B1
@ -1470,6 +1508,21 @@ shift_right_w_3 .proc
.pend .pend
shift_right_w .proc
; -- signed word variable number of shifts right
inx
ldy P8ESTACK_LO,x
bne _shift
rts
_shift lda P8ESTACK_HI+1,x
asl a
ror P8ESTACK_HI+1,x
ror P8ESTACK_LO+1,x
dey
bne _shift
rts
.pend
; support for bit shifting that is too large to be unrolled: ; support for bit shifting that is too large to be unrolled:

View File

@ -1,8 +1,6 @@
; Prog8 internal Math library routines - always included by the compiler ; Internal Math library routines - always included by the compiler
; ;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
math { math {
%asminclude "library:math.asm", "" %asminclude "library:math.asm", ""

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,84 @@
; Prog8 internal library routines - always included by the compiler ; Internal library routines - always included by the compiler
; ;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
prog8_lib { prog8_lib {
%asminclude "library:prog8_lib.asm", "" %asminclude "library:prog8_lib.asm", ""
%asminclude "library:prog8_funcs.asm", ""
uword @zp retval_interm_uw ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
word @zp retval_interm_w ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
ubyte @zp retval_interm_ub ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
byte @zp retval_interm_b ; to store intermediary expression results for return values (hopefully allocated on ZP to reduce code size)
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
%asm {{
; pattern matching of a string.
; Input: cx16.r0: A NUL-terminated, <255-length pattern
; AY: A NUL-terminated, <255-length string
;
; Output: A = 1 if the string matches the pattern, A = 0 if not.
;
; Notes: Clobbers A, X, Y. Each * in the pattern uses 4 bytes of stack.
;
; see http://6502.org/source/strings/patmatch.htm
str = P8ZP_SCRATCH_W1
stx P8ZP_SCRATCH_REG
sta str
sty str+1
lda cx16.r0
sta modify_pattern1+1
sta modify_pattern2+1
lda cx16.r0+1
sta modify_pattern1+2
sta modify_pattern2+2
jsr _match
lda #0
adc #0
ldx P8ZP_SCRATCH_REG
rts
_match
ldx #$00 ; x is an index in the pattern
ldy #$ff ; y is an index in the string
modify_pattern1
next lda $ffff,x ; look at next pattern character MODIFIED
cmp #'*' ; is it a star?
beq star ; yes, do the complicated stuff
iny ; no, let's look at the string
cmp #'?' ; is the pattern caracter a ques?
bne reg ; no, it's a regular character
lda (str),y ; yes, so it will match anything
beq fail ; except the end of string
reg cmp (str),y ; are both characters the same?
bne fail ; no, so no match
inx ; yes, keep checking
cmp #0 ; are we at end of string?
bne next ; not yet, loop
found rts ; success, return with c=1
star inx ; skip star in pattern
modify_pattern2
cmp $ffff,x ; string of stars equals one star MODIFIED
beq star ; so skip them also
stloop txa ; we first try to match with * = ""
pha ; and grow it by 1 character every
tya ; time we loop
pha ; save x and y on stack
jsr next ; recursive call
pla ; restore x and y
tay
pla
tax
bcs found ; we found a match, return with c=1
iny ; no match yet, try to grow * string
lda (str),y ; are we at the end of string?
bne stloop ; not yet, add a character
fail clc ; yes, no match found, return with c=0
rts
}}
}
} }

View File

@ -0,0 +1,235 @@
; 0-terminated string manipulation routines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
string {
asmsub length(uword string @AY) clobbers(A) -> ubyte @Y {
; Returns the number of bytes in the string.
; This value is determined during runtime and counts upto the first terminating 0 byte in the string,
; regardless of the size of the string during compilation time. Dont confuse this with len and sizeof!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
iny
bne -
+ rts
}}
}
asmsub left(uword source @R0, ubyte length @A, uword target @R1) clobbers(A, Y) {
; Copies the left side of the source string of the given length to target string.
; It is assumed the target string buffer is large enough to contain the result.
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
; Modifies in-place, doesnt return a value (so cant be used in an expression).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
ldy cx16.r0
sty P8ZP_SCRATCH_W1
ldy cx16.r0+1
sty P8ZP_SCRATCH_W1+1
ldy cx16.r1
sty P8ZP_SCRATCH_W2
ldy cx16.r1+1
sty P8ZP_SCRATCH_W2+1
tay
lda #0
sta (P8ZP_SCRATCH_W2),y
cpy #0
bne _loop
rts
_loop dey
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
cpy #0
bne _loop
+ rts
}}
; asmgen.out(" jsr prog8_lib.func_leftstr")
}
asmsub right(uword source @R0, ubyte length @A, uword target @R1) clobbers(A,Y) {
; Copies the right side of the source string of the given length to target string.
; It is assumed the target string buffer is large enough to contain the result.
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
; Modifies in-place, doesnt return a value (so cant be used in an expression).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
sta P8ZP_SCRATCH_B1
lda cx16.r0
ldy cx16.r0+1
jsr string.length
tya
sec
sbc P8ZP_SCRATCH_B1
clc
adc cx16.r0
sta P8ZP_SCRATCH_W1
lda cx16.r0+1
adc #0
sta P8ZP_SCRATCH_W1+1
ldy cx16.r1
sty P8ZP_SCRATCH_W2
ldy cx16.r1+1
sty P8ZP_SCRATCH_W2+1
ldy P8ZP_SCRATCH_B1
lda #0
sta (P8ZP_SCRATCH_W2),y
cpy #0
bne _loop
rts
_loop dey
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
cpy #0
bne _loop
+ rts
}}
}
asmsub slice(uword source @R0, ubyte start @A, ubyte length @Y, uword target @R1) clobbers(A, Y) {
; Copies a segment from the source string, starting at the given index,
; and of the given length to target string.
; It is assumed the target string buffer is large enough to contain the result.
; Also, you have to make sure yourself that start and length are within bounds of the strings.
; Modifies in-place, doesnt return a value (so cant be used in an expression).
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
; substr(source, target, start, length)
sta P8ZP_SCRATCH_B1
lda cx16.r0
sta P8ZP_SCRATCH_W1
lda cx16.r0+1
sta P8ZP_SCRATCH_W1+1
lda cx16.r1
sta P8ZP_SCRATCH_W2
lda cx16.r1+1
sta P8ZP_SCRATCH_W2+1
; adjust src location
clc
lda P8ZP_SCRATCH_W1
adc P8ZP_SCRATCH_B1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ lda #0
sta (P8ZP_SCRATCH_W2),y
beq _startloop
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
_startloop dey
cpy #$ff
bne -
rts
}}
}
asmsub find(uword string @R0, ubyte character @A) -> uword @AY {
; Locates the first position of the given character in the string,
; returns the string starting with this character or $0000 if the character is not found.
%asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
sta P8ZP_SCRATCH_B1
lda cx16.r0
ldy cx16.r0+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _notfound
cmp P8ZP_SCRATCH_B1
beq _found
iny
bne -
_notfound lda #0
ldy #0
rts
_found sty P8ZP_SCRATCH_B1
ldy P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_W1
clc
adc P8ZP_SCRATCH_B1
bcc +
iny
+ rts
}}
}
asmsub copy(uword source @R0, uword target @AY) clobbers(A) -> ubyte @Y {
; Copy a string to another, overwriting that one.
; Returns the length of the string that was copied.
; Often you dont have to call this explicitly and can just write string1 = string2
; but this function is useful if youre dealing with addresses for instance.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
ldy cx16.r0+1
jmp prog8_lib.strcpy
}}
}
asmsub compare(uword string1 @R0, uword string2 @AY) clobbers(Y) -> byte @A {
; Compares two strings for sorting.
; Returns -1 (255), 0 or 1 depeding on wether string1 sorts before, equal or after string2.
; Note that you can also directly compare strings and string values with eachother using
; comparison operators ==, < etcetera (it will use strcmp for you under water automatically).
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda cx16.r0
ldy cx16.r0+1
jmp prog8_lib.strcmp_mem
}}
}
asmsub lower(uword st @AY) {
; Lowercases the petscii string in-place.
; (for efficiency, non-letter characters > 128 will also not be left intact,
; but regular text doesn't usually contain those characters anyway.)
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _done
and #$7f
cmp #97
bcc +
cmp #123
bcs +
and #%11011111
+ sta (P8ZP_SCRATCH_W1),y
iny
bne -
_done rts
}}
}
asmsub upper(uword st @AY) {
; Uppercases the petscii string in-place.
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq _done
cmp #65
bcc +
cmp #91
bcs +
ora #%00100000
+ sta (P8ZP_SCRATCH_W1),y
iny
bne -
_done rts
}}
}
}

View File

@ -0,0 +1,50 @@
; utility debug code to print the X (evalstack) and SP (cpu stack) registers.
%import textio
test_stack {
asmsub test() {
%asm {{
stx _saveX
lda #13
jsr txt.chrout
lda #'-'
ldy #12
- jsr txt.chrout
dey
bne -
lda #13
jsr txt.chrout
lda #'x'
jsr txt.chrout
lda #'='
jsr txt.chrout
lda _saveX
jsr txt.print_ub
lda #' '
jsr txt.chrout
lda #'s'
jsr txt.chrout
lda #'p'
jsr txt.chrout
lda #'='
jsr txt.chrout
tsx
txa
jsr txt.print_ub
lda #13
jsr txt.chrout
lda #'-'
ldy #12
- jsr txt.chrout
dey
bne -
lda #13
jsr txt.chrout
ldx _saveX
rts
_saveX .byte 0
}}
}
}

View File

@ -1 +1 @@
4.6 6.3

View File

@ -1,12 +1,14 @@
package prog8 package prog8
import kotlinx.cli.* import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
import kotlinx.cli.multiple
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.compiler.CompilationResult import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.compiler.target.CompilationTarget
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Path import java.nio.file.Path
@ -32,20 +34,20 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
private fun compileMain(args: Array<String>) { private fun compileMain(args: Array<String>) {
val cli = CommandLineInterface("prog8compiler") val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
val startEmulator by cli.flagArgument("-emu", "auto-start emulator after successful compilation") val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".") val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code") val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations") val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed") val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation") val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name) val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
try { try {
cli.parse(args) cli.parse(args)
} catch (e: Exception) { } catch (e: IllegalStateException) {
System.err.println(e.message)
exitProcess(1) exitProcess(1)
} }
@ -55,32 +57,45 @@ private fun compileMain(args: Array<String>) {
exitProcess(1) exitProcess(1)
} }
if(watchMode && moduleFiles.size<=1) { if(watchMode==true) {
val watchservice = FileSystems.getDefault().newWatchService() val watchservice = FileSystems.getDefault().newWatchService()
val allImportedFiles = mutableSetOf<Path>()
while(true) { while(true) {
val filepath = pathFrom(moduleFiles.single()).normalize() println("Continuous watch mode active. Modules: $moduleFiles")
println("Continuous watch mode active. Main module: $filepath") val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
results.add(compilationResult)
}
try { val allNewlyImportedFiles = results.flatMap { it.importedFiles }
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath) allImportedFiles.addAll(allNewlyImportedFiles)
println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) { println("Imported files (now watching:)")
print(" ") for (importedFile in allImportedFiles) {
println(importedFile) print(" ")
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY) println(importedFile)
} val watchDir = importedFile.parent ?: Path.of(".")
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.") watchDir.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
}
println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
var recompile=false
while(!recompile) {
val event = watchservice.take() val event = watchservice.take()
for(changed in event.pollEvents()) { for (changed in event.pollEvents()) {
val changedPath = changed.context() as Path val changedPath = changed.context() as Path
println(" change detected: $changedPath") if(allImportedFiles.any { it.fileName == changedPath.fileName }) {
println(" change detected: $changedPath")
recompile = true
}
} }
event.reset() event.reset()
println("\u001b[H\u001b[2J") // clear the screen
} catch (x: Exception) {
throw x
} }
println("\u001b[H\u001b[2J") // clear the screen
} }
} else { } else {
@ -88,7 +103,7 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize() val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult val compilationResult: CompilationResult
try { try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath) compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
if(!compilationResult.success) if(!compilationResult.success)
exitProcess(1) exitProcess(1)
} catch (x: ParsingFailedError) { } catch (x: ParsingFailedError) {
@ -97,11 +112,11 @@ private fun compileMain(args: Array<String>) {
exitProcess(1) exitProcess(1)
} }
if (startEmulator) { if (startEmulator==true) {
if (compilationResult.programName.isEmpty()) if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.") println("\nCan't start emulator because no program was assembled.")
else if(startEmulator) { else {
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName) compilationResult.compTarget.machine.launchEmulator(compilationResult.programName)
} }
} }
} }

View File

@ -1,22 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.statements.Directive
internal class ImportedModuleDirectiveRemover: AstWalker() {
/**
* Most global directives don't apply for imported modules, so remove them
*/
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
private val noModifications = emptyList<IAstModification>()
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent as INameScope))
}
return noModifications
}
}

View File

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

View File

@ -5,17 +5,18 @@ import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() { internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
subroutineVariables.add(Pair(decl.name, decl)) subroutineVariables.add(decl.name to decl)
if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) { if (decl.value == null && !decl.autogeneratedDontRemove && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero, // a numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below, that initializes the value // unless there's already an assignment below, that initializes the value
@ -37,7 +38,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF. // But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable if(!assignment.isAugmentable
&& assignment.target.identifier != null && assignment.target.identifier != null
&& assignment.target.isInRegularRAM(program.namespace)) { && compTarget.isInRegularRAM(assignment.target, program)) {
val binExpr = assignment.value as? BinaryExpression val binExpr = assignment.value as? BinaryExpression
if (binExpr != null && binExpr.operator !in comparisonOperators) { if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) { if (binExpr.left !is BinaryExpression) {
@ -74,7 +75,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> { override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>() val decls = scope.statements.filterIsInstance<VarDecl>()
subroutineVariables.addAll(decls.map { Pair(it.name, it) }) subroutineVariables.addAll(decls.map { it.name to it })
val sub = scope.definingSubroutine() val sub = scope.definingSubroutine()
if (sub != null) { if (sub != null) {
@ -106,11 +107,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
} }
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine. // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations. // and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>() val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position) val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null if (subroutine.asmAddress == null
&& !subroutine.inline
&& subroutine.statements.isNotEmpty() && subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0 && subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return && subroutine.statements.lastOrNull { it !is VarDecl } !is Return
@ -155,7 +157,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
if(typecast.type in WordDatatypes) { if(typecast.type in WordDatatypes) {
val fcall = typecast.parent as? IFunctionCall val fcall = typecast.parent as? IFunctionCall
if (fcall != null) { if (fcall != null) {
val sub = fcall.target.targetStatement(program.namespace) as? Subroutine val sub = fcall.target.targetStatement(program) as? Subroutine
if (sub != null && sub.isAsmSubroutine) { if (sub != null && sub.isAsmSubroutine) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent)) return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
} }
@ -170,6 +172,12 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
AddressOf(typecast.expression as IdentifierReference, typecast.position), AddressOf(typecast.expression as IdentifierReference, typecast.position),
parent parent
)) ))
} else if(typecast.expression is IFunctionCall) {
return listOf(IAstModification.ReplaceNode(
typecast,
typecast.expression,
parent
))
} }
} else { } else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position) errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)

View File

@ -1,9 +1,30 @@
package prog8.compiler package prog8.compiler
import prog8.ast.AstToSourceCode
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Directive
import prog8.compiler.astprocessing.*
import prog8.compiler.functions.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.asmGeneratorFor
import prog8.optimizer.*
import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.moduleName
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.nio.file.Path import java.nio.file.Path
import kotlin.math.abs import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
enum class OutputType { enum class OutputType {
RAW, RAW,
@ -28,33 +49,283 @@ data class CompilationOptions(val output: OutputType,
val zeropage: ZeropageType, val zeropage: ZeropageType,
val zpReserved: List<IntRange>, val zpReserved: List<IntRange>,
val floats: Boolean, val floats: Boolean,
val noSysInit: Boolean) { val noSysInit: Boolean,
val compTarget: ICompilationTarget) {
var slowCodegenWarnings = false var slowCodegenWarnings = false
var optimize = false
} }
class CompilerException(message: String?) : Exception(message) class CompilerException(message: String?) : Exception(message)
fun Number.toHex(): String { class CompilationResult(val success: Boolean,
// 0..15 -> "0".."15" val programAst: Program,
// 16..255 -> "$10".."$ff" val programName: String,
// 256..65536 -> "$0100".."$ffff" val compTarget: ICompilationTarget,
// negative values are prefixed with '-'. val importedFiles: List<Path>)
val integer = this.toInt()
if(integer<0)
return '-' + abs(integer).toHex() fun compileProgram(filepath: Path,
return when (integer) { optimize: Boolean,
in 0 until 16 -> integer.toString() writeAssembly: Boolean,
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0') slowCodegenWarnings: Boolean,
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0') compilationTarget: String,
else -> throw CompilerException("number too large for 16 bits $this") outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
val compTarget =
when(compilationTarget) {
C64Target.name -> C64Target
Cx16Target.name -> Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
print("\u001b[0m") // reset
System.out.flush()
throw x
} catch (x: NotImplementedError) {
print("\u001b[91m") // bright red
println("\n* internal error: missing feature/code *")
print("\u001b[0m") // reset
System.out.flush()
throw x
} }
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
}
private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions {
lateinit var program: Program
override val names = functions.keys
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? {
val func = BuiltinFunctions[name]
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null) {
return try {
exprfunc(args, position, program, memsizer)
} catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed.
null
} catch(x: CannotEvaluateException) {
// const-evaluating the builtin function call failed.
null
}
}
else if(func.known_returntype==null)
throw IllegalArgumentException("builtin function $name can't be used here because it doesn't return a value")
}
return null
}
override fun returnType(name: String, args: MutableList<Expression>) =
builtinFunctionReturnType(name, args, program)
}
private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget): Triple<Program, CompilationOptions, List<Path>> {
val compilationTargetName = compTarget.name
println("Compiler target: $compilationTargetName. Parsing...")
val importer = ModuleImporter()
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
bf.program = programAst
importer.importModule(programAst, filepath, compTarget, compilationTargetName)
errors.report()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst, compTarget)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName))
importer.importLibraryModule(programAst, lib, compTarget, compilationTargetName)
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math", compTarget, compilationTargetName)
importer.importLibraryModule(programAst, "prog8_lib", compTarget, compilationTargetName)
errors.report()
return Triple(programAst, compilerOptions, importedFiles)
}
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
val mainModule = program.mainModule
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
System.err.println("invalid output type $outputType")
exitProcess(1)
}
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, noSysInit,
compTarget
)
}
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
errors.report()
programAst.constantFold(errors, compilerOptions.compTarget)
errors.report()
programAst.reorderStatements(errors)
errors.report()
programAst.addTypecasts(errors)
errors.report()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
errors.report()
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
errors.report()
}
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget, ::loadAsmIncludeFile)
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
errors.report()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
remover.visit(programAst)
remover.applyModifications()
errors.report()
}
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
errors.report()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
errors.report()
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
callGraph.checkRecursiveCalls(errors)
errors.report()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private fun writeAssembly(programAst: Program,
errors: IErrorReporter,
outputDir: Path,
compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
errors.report()
// printAst(programAst)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget,
programAst,
errors,
compilerOptions.compTarget.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
assembly.assemble(compilerOptions)
errors.report()
return assembly.name
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
} }
fun loadAsmIncludeFile(filename: String, source: Path): String { fun loadAsmIncludeFile(filename: String, source: Path): String {
return if (filename.startsWith("library:")) { return if (filename.startsWith("library:")) {
val resource = tryGetEmbeddedResource(filename.substring(8)) val resource = tryGetEmbeddedResource(filename.substring(8))
?: throw IllegalArgumentException("library file '$filename' not found") ?: throw IllegalArgumentException("library file '$filename' not found")
resource.bufferedReader().use { it.readText() } resource.bufferedReader().use { it.readText() }
} else { } else {
// first try in the isSameAs folder as where the containing file was imported from // first try in the isSameAs folder as where the containing file was imported from

View File

@ -1,9 +1,18 @@
package prog8.ast.base package prog8.compiler
import prog8.ast.base.Position
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
class ErrorReporter { interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun isEmpty(): Boolean
fun report()
}
internal class ErrorReporter: IErrorReporter {
private enum class MessageSeverity { private enum class MessageSeverity {
WARNING, WARNING,
ERROR ERROR
@ -13,10 +22,14 @@ class ErrorReporter {
private val messages = mutableListOf<CompilerMessage>() private val messages = mutableListOf<CompilerMessage>()
private val alreadyReportedMessages = mutableSetOf<String>() private val alreadyReportedMessages = mutableSetOf<String>()
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position)) override fun err(msg: String, position: Position) {
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position)) messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
}
override fun warn(msg: String, position: Position) {
messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
}
fun handle() { override fun report() {
var numErrors = 0 var numErrors = 0
var numWarnings = 0 var numWarnings = 0
messages.forEach { messages.forEach {
@ -40,5 +53,5 @@ class ErrorReporter {
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.") throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
} }
fun isEmpty() = messages.isEmpty() override fun isEmpty() = messages.isEmpty()
} }

View File

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

View File

@ -21,7 +21,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int { fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"} assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE) if(options.zeropage==ZeropageType.DONTUSE)
@ -64,7 +64,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int { private fun makeAllocation(address: Int, size: Int, datatype: DataType, name: String?): Int {
free.removeAll(address until address+size) free.removeAll(address until address+size)
allocations[address] = Pair(name ?: "<unnamed>", datatype) allocations[address] = (name ?: "<unnamed>") to datatype
return address return address
} }

View File

@ -1,4 +1,4 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Module import prog8.ast.Module
@ -6,16 +6,21 @@ import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target import prog8.compiler.target.Cx16Target
import prog8.functions.BuiltinFunctions import prog8.compiler.target.ICompilationTarget
import java.io.File import java.io.File
internal class AstChecker(private val program: Program, internal class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions, private val compilerOptions: CompilationOptions,
private val errors: ErrorReporter) : IAstVisitor { private val errors: IErrorReporter,
private val compTarget: ICompilationTarget
) : IAstVisitor {
override fun visit(program: Program) { override fun visit(program: Program) {
assert(program === this.program) assert(program === this.program)
@ -34,37 +39,6 @@ internal class AstChecker(private val program: Program,
if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty()) if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
errors.err("program entrypoint subroutine can't have parameters and/or return values", startSub.position) errors.err("program entrypoint subroutine can't have parameters and/or return values", startSub.position)
} }
// the main module cannot contain 'regular' statements (they will never be executed!)
for (statement in mainBlock.statements) {
val ok = when (statement) {
is Block -> true
is Directive -> true
is Label -> true
is VarDecl -> true
is InlineAssembly -> true
is INameScope -> true
is NopStatement -> true
else -> false
}
if (!ok) {
errors.err("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position)
break
}
}
}
// there can be an optional single 'irq' block with a 'irq' subroutine in it,
// which will be used as the 60hz irq routine in the vm if it's present
val irqBlocks = program.modules.flatMap { it.statements }.filter { it is Block && it.name=="irq" }.map { it as Block }
if(irqBlocks.size>1)
errors.err("more than one 'irq' block", irqBlocks[0].position)
for(irqBlock in irqBlocks) {
val irqSub = irqBlock.subScope("irq") as? Subroutine
if (irqSub != null) {
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
}
} }
super.visit(program) super.visit(program)
@ -116,7 +90,7 @@ internal class AstChecker(private val program: Program,
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) { if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
errors.err("can only loop over an iterable type", forLoop.position) errors.err("can only loop over an iterable type", forLoop.position)
} else { } else {
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace) val loopvar = forLoop.loopVar.targetVarDecl(program)
if(loopvar==null || loopvar.type== VarDeclType.CONST) { if(loopvar==null || loopvar.type== VarDeclType.CONST) {
errors.err("for loop requires a variable to loop with", forLoop.position) errors.err("for loop requires a variable to loop with", forLoop.position)
} else { } else {
@ -167,24 +141,44 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(jump: Jump) { override fun visit(jump: Jump) {
if(jump.identifier!=null) { val ident = jump.identifier
val targetStatement = checkFunctionOrLabelExists(jump.identifier, jump) if(ident!=null) {
val targetStatement = checkFunctionOrLabelExists(ident, jump)
if(targetStatement!=null) { if(targetStatement!=null) {
if(targetStatement is BuiltinFunctionStatementPlaceholder) if(targetStatement is BuiltinFunctionStatementPlaceholder)
errors.err("can't jump to a builtin function", jump.position) errors.err("can't jump to a builtin function", jump.position)
} }
} }
if(jump.address!=null && (jump.address < 0 || jump.address > 65535)) val addr = jump.address
if(addr!=null && (addr < 0 || addr > 65535))
errors.err("jump address must be valid integer 0..\$ffff", jump.position) errors.err("jump address must be valid integer 0..\$ffff", jump.position)
super.visit(jump) super.visit(jump)
} }
override fun visit(block: Block) { override fun visit(block: Block) {
if(block.address!=null && (block.address<0 || block.address>65535)) { val addr = block.address
if(addr!=null && (addr<0 || addr>65535)) {
errors.err("block memory address must be valid integer 0..\$ffff", block.position) errors.err("block memory address must be valid integer 0..\$ffff", block.position)
} }
for (statement in block.statements) {
val ok = when (statement) {
is Block,
is Directive,
is Label,
is VarDecl,
is InlineAssembly,
is INameScope,
is NopStatement -> true
else -> false
}
if (!ok) {
errors.err("non-declarative statement occurs in block scope, where it will never be executed. Move it to a subroutine instead.", statement.position)
break
}
}
super.visit(block) super.visit(block)
} }
@ -202,10 +196,20 @@ internal class AstChecker(private val program: Program,
if(subroutine.name in BuiltinFunctions) if(subroutine.name in BuiltinFunctions)
err("cannot redefine a built-in function") err("cannot redefine a built-in function")
if(subroutine.parameters.size>16)
err("subroutines are limited to 16 parameters")
val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet() val uniqueNames = subroutine.parameters.asSequence().map { it.name }.toSet()
if(uniqueNames.size!=subroutine.parameters.size) if(uniqueNames.size!=subroutine.parameters.size)
err("parameter names must be unique") err("parameter names must be unique")
if(subroutine.inline) {
if (subroutine.containsDefinedVariables())
err("can't inline a subroutine that defines variables")
if (!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty())
err("can't inline a non-asm subroutine that has parameters")
}
super.visit(subroutine) super.visit(subroutine)
// user-defined subroutines can only have zero or one return type // user-defined subroutines can only have zero or one return type
@ -214,13 +218,13 @@ internal class AstChecker(private val program: Program,
err("subroutines can only have one return value") err("subroutines can only have one return value")
// subroutine must contain at least one 'return' or 'goto' // subroutine must contain at least one 'return' or 'goto'
// (or if it has an asm block, that must contain a 'rts' or 'jmp') // (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
if(subroutine.statements.count { it is Return || it is Jump } == 0) { if(subroutine.statements.count { it is Return || it is Jump } == 0) {
if (subroutine.amountOfRtsInAsm() == 0) { if (subroutine.amountOfRtsInAsm() == 0) {
if (subroutine.returntypes.isNotEmpty()) { if (subroutine.returntypes.isNotEmpty()) {
// for asm subroutines with an address, no statement check is possible. // for asm subroutines with an address, no statement check is possible.
if (subroutine.asmAddress == null) if (subroutine.asmAddress == null && !subroutine.inline)
err("subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)") err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or rts/jmp/bra in case of %asm)")
} }
} }
} }
@ -250,19 +254,19 @@ internal class AstChecker(private val program: Program,
err("parameter '${param.first.name}' should be ubyte") err("parameter '${param.first.name}' should be ubyte")
} }
} }
for(ret in subroutine.returntypes.withIndex().zip(subroutine.asmReturnvaluesRegisters)) { subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).forEachIndexed { index, pair ->
if(ret.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) { if(pair.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if (ret.first.value != DataType.UBYTE && ret.first.value != DataType.BYTE) if (pair.first != DataType.UBYTE && pair.first != DataType.BYTE)
err("return value #${ret.first.index + 1} should be (u)byte") err("return value #${index + 1} should be (u)byte")
} }
else if(ret.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) { else if(pair.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (ret.first.value != DataType.UWORD && ret.first.value != DataType.WORD if (pair.first != DataType.UWORD && pair.first != DataType.WORD
&& ret.first.value != DataType.STR && ret.first.value !in ArrayDatatypes && ret.first.value != DataType.FLOAT) && pair.first != DataType.STR && pair.first !in ArrayDatatypes && pair.first != DataType.FLOAT)
err("return value #${ret.first.index + 1} should be (u)word/address") err("return value #${index + 1} should be (u)word/address")
} }
else if(ret.second.statusflag!=null) { else if(pair.second.statusflag!=null) {
if (ret.first.value != DataType.UBYTE) if (pair.first != DataType.UBYTE)
err("return value #${ret.first.index + 1} should be ubyte") err("return value #${index + 1} should be ubyte")
} }
} }
@ -288,9 +292,28 @@ internal class AstChecker(private val program: Program,
regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1 regCounts[CpuRegister.X]=regCounts.getValue(CpuRegister.X)+1
regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1 regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
} }
null -> RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> { /* no sensible way to count this */ }
if(p.statusflag!=null) RegisterOrPair.R0,
statusflagCounts[p.statusflag] = statusflagCounts.getValue(p.statusflag) + 1 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 -> { /* no sensible way to count this */ }
null -> {
val statusf = p.statusflag
if (statusf != null)
statusflagCounts[statusf] = statusflagCounts.getValue(statusf) + 1
}
} }
} }
} }
@ -315,10 +338,6 @@ internal class AstChecker(private val program: Program,
if(statusFlagsNoCarry.isNotEmpty()) if(statusFlagsNoCarry.isNotEmpty())
err("can only use Carry as status flag parameter") err("can only use Carry as status flag parameter")
val carryParameter = subroutine.asmParameterRegisters.singleOrNull { it.statusflag==Statusflag.Pc }
if(carryParameter!=null && carryParameter !== subroutine.asmParameterRegisters.last())
err("carry parameter has to come last")
} else { } else {
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly // Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD). // Instead, their reference (address) should be passed (as an UWORD).
@ -340,15 +359,22 @@ internal class AstChecker(private val program: Program,
super.visit(whileLoop) super.visit(whileLoop)
} }
override fun visit(repeatLoop: RepeatLoop) {
val iterations = repeatLoop.iterations?.constValue(program)
if(iterations != null && iterations.number.toInt() > 65535)
errors.err("repeat cannot go over 65535 iterations", iterations.position)
super.visit(repeatLoop)
}
override fun visit(assignment: Assignment) { override fun visit(assignment: Assignment) {
if(assignment.value is FunctionCall) { if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace) val stmt = (assignment.value as FunctionCall).target.targetStatement(program)
if (stmt is Subroutine) { if (stmt is Subroutine) {
val idt = assignment.target.inferType(program, assignment) val idt = assignment.target.inferType(program)
if(!idt.isKnown) { if(!idt.isKnown) {
errors.err("return type mismatch", assignment.value.position) errors.err("return type mismatch", assignment.value.position)
} }
if(stmt.returntypes.size <= 1 && stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) { if(stmt.returntypes.size <= 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(DataType.BYTE)) {
errors.err("return type mismatch", assignment.value.position) errors.err("return type mismatch", assignment.value.position)
} }
} }
@ -356,7 +382,7 @@ internal class AstChecker(private val program: Program,
val targetIdent = assignment.target.identifier val targetIdent = assignment.target.identifier
if(targetIdent!=null) { if(targetIdent!=null) {
val targetVar = targetIdent.targetVarDecl(program.namespace) val targetVar = targetIdent.targetVarDecl(program)
if(targetVar?.struct != null) { if(targetVar?.struct != null) {
val sourceStructLv = assignment.value as? ArrayLiteralValue val sourceStructLv = assignment.value as? ArrayLiteralValue
if (sourceStructLv != null) { if (sourceStructLv != null) {
@ -365,7 +391,7 @@ internal class AstChecker(private val program: Program,
} else { } else {
val sourceIdent = assignment.value as? IdentifierReference val sourceIdent = assignment.value as? IdentifierReference
if (sourceIdent != null) { if (sourceIdent != null) {
val sourceVar = sourceIdent.targetVarDecl(program.namespace) val sourceVar = sourceIdent.targetVarDecl(program)
if (sourceVar?.struct != null) { if (sourceVar?.struct != null) {
if (sourceVar.struct !== targetVar.struct) if (sourceVar.struct !== targetVar.struct)
errors.err("assignment of different struct types", assignment.position) errors.err("assignment of different struct types", assignment.position)
@ -378,12 +404,12 @@ internal class AstChecker(private val program: Program,
} }
} }
val targetDt = assignment.target.inferType(program, assignment) val targetDt = assignment.target.inferType(program)
val valueDt = assignment.value.inferType(program) val valueDt = assignment.value.inferType(program)
if(valueDt.isKnown && valueDt != targetDt) { if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes) if(targetDt.typeOrElse(DataType.STRUCT) in IterableDatatypes)
errors.err("cannot assign value to string or array", assignment.value.position) errors.err("cannot assign value to string or array", assignment.value.position)
else else if(!(valueDt.istype(DataType.STR) && targetDt.istype(DataType.UWORD)))
errors.err("value's type doesn't match target", assignment.value.position) errors.err("value's type doesn't match target", assignment.value.position)
} }
@ -433,7 +459,7 @@ internal class AstChecker(private val program: Program,
if (assignment is Assignment) { if (assignment is Assignment) {
val targetDatatype = assignTarget.inferType(program, assignment) val targetDatatype = assignTarget.inferType(program)
if (targetDatatype.isKnown) { if (targetDatatype.isKnown) {
val constVal = assignment.value.constValue(program) val constVal = assignment.value.constValue(program)
if (constVal != null) { if (constVal != null) {
@ -453,16 +479,13 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(addressOf: AddressOf) { override fun visit(addressOf: AddressOf) {
val variable=addressOf.identifier.targetVarDecl(program.namespace) val variable=addressOf.identifier.targetVarDecl(program)
if(variable==null) if(variable!=null
errors.err("pointer-of operand must be the name of a heap variable", addressOf.position) && variable.datatype !in ArrayDatatypes
else { && variable.type!=VarDeclType.MEMORY
if(variable.datatype !in ArrayDatatypes && variable.struct == null
&& variable.type!=VarDeclType.MEMORY && variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
&& variable.struct == null
&& variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
errors.err("invalid pointer-of operand type", addressOf.position) errors.err("invalid pointer-of operand type", addressOf.position)
}
super.visit(addressOf) super.visit(addressOf)
} }
@ -584,7 +607,7 @@ internal class AstChecker(private val program: Program,
err("memory address must be valid integer 0..\$ffff", decl.value?.position) err("memory address must be valid integer 0..\$ffff", decl.value?.position)
} }
} else { } else {
err("value of memory mapped variable can only be a number, perhaps you meant to use an address pointer type instead?", decl.value?.position) err("value of memory mapped variable can only be a fixed number, perhaps you meant to use an address pointer type instead?", decl.value?.position)
} }
} }
} }
@ -593,8 +616,8 @@ internal class AstChecker(private val program: Program,
if(declValue!=null && decl.type==VarDeclType.VAR) { if(declValue!=null && decl.type==VarDeclType.VAR) {
if(decl.datatype==DataType.STRUCT) { if(decl.datatype==DataType.STRUCT) {
val valueIdt = declValue.inferType(program) val valueIdt = declValue.inferType(program)
if(valueIdt.isUnknown) if(!valueIdt.isKnown)
throw AstException("invalid value type") throw AstException("unknown dt")
val valueDt = valueIdt.typeOrElse(DataType.STRUCT) val valueDt = valueIdt.typeOrElse(DataType.STRUCT)
if(valueDt !in ArrayDatatypes) if(valueDt !in ArrayDatatypes)
err("initialisation of struct should be with array value", declValue.position) err("initialisation of struct should be with array value", declValue.position)
@ -603,23 +626,28 @@ internal class AstChecker(private val program: Program,
} }
} }
// array length limits // array length limits and constant lenghts
if(decl.isArray) { if(decl.isArray) {
val length = decl.arraysize!!.constIndex() ?: 1 val length = decl.arraysize!!.constIndex()
when (decl.datatype) { if(length==null)
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> { err("array length must be a constant")
if(length==0 || length>256) else {
err("string and byte array length must be 1-256") when (decl.datatype) {
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B -> {
if (length == 0 || length > 256)
err("string and byte array length must be 1-256")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
if (length == 0 || length > 128)
err("word array length must be 1-128")
}
DataType.ARRAY_F -> {
if (length == 0 || length > 51)
err("float array length must be 1-51")
}
else -> {
}
} }
DataType.ARRAY_UW, DataType.ARRAY_W -> {
if(length==0 || length>128)
err("word array length must be 1-128")
}
DataType.ARRAY_F -> {
if(length==0 || length>51)
err("float array length must be 1-51")
}
else -> {}
} }
} }
@ -711,7 +739,7 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level") err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty()) if(directive.args.isEmpty())
err("missing option directive argument(s)") err("missing option directive argument(s)")
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit")}.any { !it }) else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
err("invalid option directive argument(s)") err("invalid option directive argument(s)")
} }
"%target" -> { "%target" -> {
@ -746,7 +774,7 @@ internal class AstChecker(private val program: Program,
fun isPassByReferenceElement(e: Expression): Boolean { fun isPassByReferenceElement(e: Expression): Boolean {
if(e is IdentifierReference) { if(e is IdentifierReference) {
val decl = e.targetVarDecl(program.namespace)!! val decl = e.targetVarDecl(program)!!
return decl.datatype in PassByReferenceDatatypes return decl.datatype in PassByReferenceDatatypes
} }
return e is StringLiteralValue return e is StringLiteralValue
@ -754,7 +782,7 @@ internal class AstChecker(private val program: Program,
if(array.parent is VarDecl) { if(array.parent is VarDecl) {
if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) }) if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
errors.err("array literal for variable initialization contains invalid types", array.position) errors.err("array literal for variable initialization contains non-constant elements", array.position)
} else if(array.parent is ForLoop) { } else if(array.parent is ForLoop) {
if (!array.value.all { it.constValue(program) != null }) if (!array.value.all { it.constValue(program) != null })
errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position) errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position)
@ -769,7 +797,11 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(expr: PrefixExpression) { override fun visit(expr: PrefixExpression) {
val dt = expr.inferType(program).typeOrElse(DataType.STRUCT) val idt = expr.inferType(program)
if(!idt.isKnown)
return // any error should be reported elsewhere
val dt = idt.typeOrElse(DataType.STRUCT)
if(expr.operator=="-") { if(expr.operator=="-") {
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) { if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
errors.err("can only take negative of a signed number type", expr.position) errors.err("can only take negative of a signed number type", expr.position)
@ -839,6 +871,10 @@ internal class AstChecker(private val program: Program,
override fun visit(typecast: TypecastExpression) { override fun visit(typecast: TypecastExpression) {
if(typecast.type in IterableDatatypes) if(typecast.type in IterableDatatypes)
errors.err("cannot type cast to string or array type", typecast.position) errors.err("cannot type cast to string or array type", typecast.position)
if(!typecast.expression.inferType(program).isKnown)
errors.err("this expression doesn't return a value", typecast.expression.position)
super.visit(typecast) super.visit(typecast)
} }
@ -891,12 +927,12 @@ internal class AstChecker(private val program: Program,
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position) errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
} }
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program) val error = VerifyFunctionArgTypes.checkTypes(functionCall, program)
if(error!=null) if(error!=null)
errors.err(error, functionCall.position) errors.err(error, functionCall.position)
// check the functions that return multiple returnvalues. // check the functions that return multiple returnvalues.
val stmt = functionCall.target.targetStatement(program.namespace) val stmt = functionCall.target.targetStatement(program)
if (stmt is Subroutine) { if (stmt is Subroutine) {
if (stmt.returntypes.size > 1) { if (stmt.returntypes.size > 1) {
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine. // Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
@ -924,11 +960,19 @@ internal class AstChecker(private val program: Program,
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement) val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
if(targetStatement!=null) if(targetStatement!=null)
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position) checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
if(!functionCallStatement.void && targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) { if (!functionCallStatement.void) {
if(targetStatement.returntypes.size==1) // check for unused return values
errors.warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position) if (targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
else if(targetStatement.returntypes.size==1)
errors.warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position) errors.warn("result value of subroutine call is discarded (use void?)", functionCallStatement.position)
else
errors.warn("result values of subroutine call are discarded (use void?)", functionCallStatement.position)
}
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
val rt = builtinFunctionReturnType(targetStatement.name, functionCallStatement.args, program)
if(rt.isKnown)
errors.warn("result value of a function call is discarded (use void?)", functionCallStatement.position)
}
} }
if(functionCallStatement.target.nameInSource.last() == "sort") { if(functionCallStatement.target.nameInSource.last() == "sort") {
@ -946,7 +990,8 @@ internal class AstChecker(private val program: Program,
} }
} }
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, functionCallStatement.definingScope(), program) val error =
VerifyFunctionArgTypes.checkTypes(functionCallStatement, program)
if(error!=null) { if(error!=null) {
errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position) errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position)
} }
@ -973,7 +1018,7 @@ internal class AstChecker(private val program: Program,
errors.err("swap requires args of numerical type", position) errors.err("swap requires args of numerical type", position)
} }
else if(target.name=="all" || target.name=="any") { else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) { if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program)?.datatype == DataType.STR) {
errors.err("any/all on a string is useless (is always true unless the string is empty)", position) errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
} }
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) { if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
@ -981,14 +1026,34 @@ internal class AstChecker(private val program: Program,
} }
} }
} else if(target is Subroutine) { } else if(target is Subroutine) {
if(target.regXasResult())
errors.warn("subroutine call return value in X register is discarded and replaced by 0", position)
if(target.isAsmSubroutine) { if(target.isAsmSubroutine) {
for (arg in args.withIndex().zip(target.parameters)) { for (arg in args.zip(target.parameters)) {
val argIDt = arg.first.value.inferType(program) val argIDt = arg.first.inferType(program)
if (!argIDt.isKnown) if (!argIDt.isKnown)
return return
} }
// check that cx16 virtual registers aren't used as arguments in a conflicting way
val params = target.asmParameterRegisters.withIndex().toList()
for(arg in args.withIndex()) {
var ident: IdentifierReference? = null
if(arg.value is IdentifierReference)
ident = arg.value as IdentifierReference
else if(arg.value is FunctionCall) {
val fcall = arg.value as FunctionCall
if(fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))
ident = fcall.args[0] as? IdentifierReference
}
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].toUpperCase())
val same = params.filter { it.value.registerOrPair==reg }
for(s in same) {
if(s.index!=arg.index) {
errors.err("conflicting register $reg used as argument but is also a target register for another parameter", ident.position)
}
}
}
}
} }
} }
} }
@ -1008,7 +1073,7 @@ internal class AstChecker(private val program: Program,
} }
} }
} else if(postIncrDecr.target.arrayindexed != null) { } else if(postIncrDecr.target.arrayindexed != null) {
val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program.namespace) val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program)
if(target==null) { if(target==null) {
errors.err("undefined symbol", postIncrDecr.position) errors.err("undefined symbol", postIncrDecr.position)
} }
@ -1023,7 +1088,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) { override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
val target = arrayIndexedExpression.arrayvar.targetStatement(program.namespace) val target = arrayIndexedExpression.arrayvar.targetStatement(program)
if(target is VarDecl) { if(target is VarDecl) {
if(target.datatype !in IterableDatatypes) if(target.datatype !in IterableDatatypes)
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position) errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
@ -1063,12 +1128,18 @@ internal class AstChecker(private val program: Program,
val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT) val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT)
if(conditionType !in IntegerDatatypes) if(conditionType !in IntegerDatatypes)
errors.err("when condition must be an integer value", whenStatement.position) errors.err("when condition must be an integer value", whenStatement.position)
val choiceValues = whenStatement.choiceValues(program) val tally = mutableSetOf<Int>()
val occurringValues = choiceValues.map {it.first} for((choices, choiceNode) in whenStatement.choiceValues(program)) {
val tally = choiceValues.associate { it.second to occurringValues.count { ov->it.first==ov} } if(choices!=null) {
tally.filter { it.value>1 }.forEach { for (c in choices) {
errors.err("choice value occurs multiple times", it.key.position) if(c in tally)
errors.err("choice value already occurs earlier", choiceNode.position)
else
tally.add(c)
}
}
} }
if(whenStatement.choices.isEmpty()) if(whenStatement.choices.isEmpty())
errors.err("empty when statement", whenStatement.position) errors.err("empty when statement", whenStatement.position)
@ -1115,7 +1186,7 @@ internal class AstChecker(private val program: Program,
} }
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? { private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
val targetStatement = target.targetStatement(program.namespace) val targetStatement = target.targetStatement(program)
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder) if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
return targetStatement return targetStatement
else if(targetStatement==null) else if(targetStatement==null)
@ -1205,7 +1276,7 @@ internal class AstChecker(private val program: Program,
// check if the floating point values are all within range // check if the floating point values are all within range
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray() val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
if(doubles.any { it < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || it > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE }) if(doubles.any { it < compTarget.machine.FLOAT_MAX_NEGATIVE || it > compTarget.machine.FLOAT_MAX_POSITIVE })
return err("floating point value overflow") return err("floating point value overflow")
return true return true
} }
@ -1218,7 +1289,7 @@ internal class AstChecker(private val program: Program,
for(elt in value.value.zip(struct.statements)) { for(elt in value.value.zip(struct.statements)) {
val vardecl = elt.second as VarDecl val vardecl = elt.second as VarDecl
val valuetype = elt.first.inferType(program) val valuetype = elt.first.inferType(program)
if (!valuetype.isKnown || !(valuetype.typeOrElse(DataType.STRUCT) isAssignableTo vardecl.datatype)) { if (!valuetype.isKnown || valuetype isNotAssignableTo vardecl.datatype) {
errors.err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position) errors.err("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position)
return false return false
} }
@ -1279,7 +1350,7 @@ internal class AstChecker(private val program: Program,
val array = value.value.map { val array = value.value.map {
when (it) { when (it) {
is NumericLiteralValue -> it.number.toInt() is NumericLiteralValue -> it.number.toInt()
is AddressOf -> it.identifier.heapId(program.namespace) is AddressOf -> it.identifier.hashCode() and 0xffff
is TypecastExpression -> { is TypecastExpression -> {
val constVal = it.expression.constValue(program) val constVal = it.expression.constValue(program)
val cast = constVal?.cast(it.type) val cast = constVal?.cast(it.type)
@ -1333,12 +1404,15 @@ internal class AstChecker(private val program: Program,
if(sourceDatatype==DataType.STRUCT) { if(sourceDatatype==DataType.STRUCT) {
val structLv = sourceValue as ArrayLiteralValue val structLv = sourceValue as ArrayLiteralValue
val numValues = structLv.value.size val numValues = structLv.value.size
val targetstruct = target.identifier!!.targetVarDecl(program.namespace)!!.struct!! val targetstruct = target.identifier!!.targetVarDecl(program)!!.struct!!
return targetstruct.numberOfElements == numValues return targetstruct.numberOfElements == numValues
} }
false false
} }
else -> errors.err("cannot assign new value to variable of type $targetDatatype", position) else -> {
errors.err("cannot assign new value to variable of type $targetDatatype", position)
false
}
} }
if(result) if(result)
@ -1350,9 +1424,7 @@ internal class AstChecker(private val program: Program,
else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes) else if(sourceDatatype== DataType.FLOAT && targetDatatype in IntegerDatatypes)
errors.err("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position) errors.err("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)
else { else {
if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes) if(targetDatatype!=DataType.UWORD && sourceDatatype !in PassByReferenceDatatypes)
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}, perhaps forgot '&' ?", position)
else
errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position) errors.err("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position)
} }

View File

@ -1,31 +1,32 @@
package prog8.ast.base package prog8.compiler.astprocessing
import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.processing.* import prog8.ast.base.FatalAstException
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.compiler.CompilationOptions
import prog8.compiler.BeforeAsmGenerationAstChanger import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) { internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
val checker = AstChecker(this, compilerOptions, errors) val checker = AstChecker(this, compilerOptions, errors, compTarget)
checker.visit(this) checker.visit(this)
} }
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) { internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
val fixer = BeforeAsmGenerationAstChanger(this, errors) val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
fixer.visit(this) fixer.visit(this)
fixer.applyModifications() fixer.applyModifications()
} }
internal fun Program.reorderStatements(errors: ErrorReporter) { internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors) val reorder = StatementReorderer(this, errors)
reorder.visit(this) reorder.visit(this)
reorder.applyModifications() reorder.applyModifications()
} }
internal fun Program.addTypecasts(errors: ErrorReporter) { internal fun Program.addTypecasts(errors: IErrorReporter) {
val caster = TypecastsAdder(this, errors) val caster = TypecastsAdder(this, errors)
caster.visit(this) caster.visit(this)
caster.applyModifications() caster.applyModifications()
@ -36,15 +37,9 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this) fixer.visit(this)
} }
internal fun Module.checkImportedValid() { internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) {
val imr = ImportedModuleDirectiveRemover()
imr.visit(this, this.parent)
imr.applyModifications()
}
internal fun Program.checkIdentifiers(errors: ErrorReporter) { val checker2 = AstIdentifiersChecker(this, errors, compTarget)
val checker2 = AstIdentifiersChecker(this, errors)
checker2.visit(this) checker2.visit(this)
if(errors.isEmpty()) { if(errors.isEmpty()) {

View File

@ -1,14 +1,20 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.DataType
import prog8.ast.expressions.* import prog8.ast.base.NumericDatatypes
import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget import prog8.ast.walk.IAstVisitor
import prog8.functions.BuiltinFunctions import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.target.ICompilationTarget
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor { internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
private var blocks = mutableMapOf<String, Block>() private var blocks = mutableMapOf<String, Block>()
private fun nameError(name: String, position: Position, existing: Statement) { private fun nameError(name: String, position: Position, existing: Statement) {
@ -22,7 +28,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
} }
override fun visit(block: Block) { override fun visit(block: Block) {
if(block.name in CompilationTarget.instance.machine.opcodeNames) if(block.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position) errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
val existing = blocks[block.name] val existing = blocks[block.name]
@ -31,14 +37,21 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
else else
blocks[block.name] = block blocks[block.name] = block
if(!block.isInLibrary) {
val libraries = program.modules.filter { it.isLibraryModule }
val libraryBlockNames = libraries.flatMap { it.statements.filterIsInstance<Block>().map { b -> b.name } }
if(block.name in libraryBlockNames)
errors.err("block is already defined in an included library module", block.position)
}
super.visit(block) super.visit(block)
} }
override fun visit(directive: Directive) { override fun visit(directive: Directive) {
if(directive.directive=="%target") { if(directive.directive=="%target") {
val compatibleTarget = directive.args.single().name val compatibleTarget = directive.args.single().name
if (compatibleTarget != CompilationTarget.instance.name) if (compatibleTarget != compTarget.name)
errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position) errors.err("module's compilation target ($compatibleTarget) differs from active target (${compTarget.name})", directive.position)
} }
super.visit(directive) super.visit(directive)
@ -50,7 +63,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if(decl.name in BuiltinFunctions) if(decl.name in BuiltinFunctions)
errors.err("builtin function cannot be redefined", decl.position) errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in CompilationTarget.instance.machine.opcodeNames) if(decl.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position) errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
if(decl.datatype==DataType.STRUCT) { if(decl.datatype==DataType.STRUCT) {
@ -89,7 +102,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
} }
override fun visit(subroutine: Subroutine) { override fun visit(subroutine: Subroutine) {
if(subroutine.name in CompilationTarget.instance.machine.opcodeNames) { if(subroutine.name in compTarget.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position) errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) { } else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined // the builtin functions can't be redefined
@ -112,9 +125,12 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
val labelOrVar = subroutine.getLabelOrVariable(name) val labelOrVar = subroutine.getLabelOrVariable(name)
if(labelOrVar!=null && labelOrVar.position != subroutine.position) if(labelOrVar!=null && labelOrVar.position != subroutine.position)
nameError(name, labelOrVar.position, subroutine) nameError(name, labelOrVar.position, subroutine)
val sub = subroutine.statements.singleOrNull { it is Subroutine && it.name==name} val sub = subroutine.statements.firstOrNull { it is Subroutine && it.name==name}
if(sub!=null) if(sub!=null)
nameError(name, sub.position, subroutine) nameError(name, subroutine.position, sub)
val block = program.allBlocks().firstOrNull { it.name==name }
if(block!=null)
nameError(name, subroutine.position, block)
} }
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) { if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
@ -126,7 +142,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
} }
override fun visit(label: Label) { override fun visit(label: Label) {
if(label.name in CompilationTarget.instance.machine.opcodeNames) if(label.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position) errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) { if(label.name in BuiltinFunctions) {

View File

@ -1,10 +1,16 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.DataType
import prog8.ast.expressions.* import prog8.ast.expressions.BinaryExpression
import prog8.ast.statements.* import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.ParameterVarDecl
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class AstVariousTransforms(private val program: Program) : AstWalker() { internal class AstVariousTransforms(private val program: Program) : AstWalker() {
@ -26,7 +32,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
} }
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// For non-kernel subroutines and non-asm parameters: // For non-kernal subroutines and non-asm parameters:
// inject subroutine params as local variables (if they're not there yet). // inject subroutine params as local variables (if they're not there yet).
val symbolsInSub = subroutine.allDefinedSymbols() val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet() val namesInSub = symbolsInSub.map{ it.first }.toSet()

View File

@ -1,10 +1,15 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.DataType
import prog8.ast.expressions.* import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.statements.* import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.VarDecl
import prog8.ast.statements.WhenChoice
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
@ -12,13 +17,10 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> { override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) { if(string.parent !is VarDecl && string.parent !is WhenChoice) {
// replace the literal string by a identifier reference to a new local vardecl // replace the literal string by a identifier reference to the interned string
val vardecl = VarDecl.createAuto(string) val scopedName = program.internString(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position) val identifier = IdentifierReference(scopedName, string.position)
return listOf( return listOf(IAstModification.ReplaceNode(string, identifier, parent))
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope())
)
} }
return noModifications return noModifications
} }

View File

@ -1,4 +1,4 @@
package prog8.ast.processing package prog8.compiler.astprocessing
/* /*

View File

@ -1,12 +1,16 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() { internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
// Reorders the statements in a way the compiler needs. // Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set. // - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last. // - library blocks are put last.
@ -68,10 +72,36 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
) )
} }
} }
val subs = subroutine.statements.filterIsInstance<Subroutine>()
if(subs.isNotEmpty()) {
// all subroutines defined within this subroutine are moved to the end
return subs.map { IAstModification.Remove(it, subroutine) } +
subs.map { IAstModification.InsertLast(it, subroutine) }
}
return noModifications return noModifications
} }
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> { override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
// rewrite pointervar[index] into @(pointervar+index)
val indexer = arrayIndexedExpression.indexer
val index = (indexer.indexNum ?: indexer.indexVar)!!
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", index, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// we're part of the target of an assignment, we have to actually change the assign target itself
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
val memread = DirectMemoryRead(add, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(arrayIndexedExpression, memread, parent))
}
}
when (val expr2 = arrayIndexedExpression.indexer.origExpression) { when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
is NumericLiteralValue -> { is NumericLiteralValue -> {
arrayIndexedExpression.indexer.indexNum = expr2 arrayIndexedExpression.indexer.indexNum = expr2
@ -91,41 +121,110 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
} }
} }
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
// when using a simple bit shift and assigning it to a variable of a different type,
// try to make the bit shifting 'wide enough' to fall into the variable's type.
// with this, for instance, uword x = 1 << 10 will result in 1024 rather than 0 (the ubyte result).
if(expr.operator=="<<" || expr.operator==">>") {
val leftDt = expr.left.inferType(program)
when (parent) {
is Assignment -> {
val targetDt = parent.target.inferType(program)
if(leftDt != targetDt) {
val cast = TypecastExpression(expr.left, targetDt.typeOrElse(DataType.STRUCT), true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is VarDecl -> {
if(!leftDt.istype(parent.datatype)) {
val cast = TypecastExpression(expr.left, parent.datatype, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is IFunctionCall -> {
val argnum = parent.args.indexOf(expr)
when (val callee = parent.target.targetStatement(program)) {
is Subroutine -> {
val paramType = callee.parameters[argnum].type
if(leftDt isAssignableTo paramType) {
val cast = TypecastExpression(expr.left, paramType, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(callee.name)
val paramTypes = func.parameters[argnum].possibleDatatypes
for(type in paramTypes) {
if(leftDt isAssignableTo type) {
val cast = TypecastExpression(expr.left, type, true, parent.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
}
}
else -> throw FatalAstException("weird callee")
}
}
else -> return noModifications
}
}
else if(expr.operator in logicalOperators) {
// make sure that logical expressions like "var and other-logical-expression
// is rewritten as "var!=0 and other-logical-expression", to avoid bitwise boolean and
// generating the wrong results later
fun wrapped(expr: Expression): Expression =
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position)
fun isLogicalExpr(expr: Expression?): Boolean {
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
return true
if(expr is PrefixExpression && expr.operator in logicalOperators)
return true
return false
}
return if(isLogicalExpr(expr.left)) {
if(isLogicalExpr(expr.right))
noModifications
else
listOf(IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr))
} else {
if(isLogicalExpr(expr.right))
listOf(IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr))
else {
listOf(
IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr),
IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr)
)
}
}
}
return noModifications
}
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> { private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>() val modifications = mutableListOf<IAstModification>()
val subroutine = expr.definingSubroutine()!! val subroutine = expr.definingSubroutine()!!
val statement = expr.containingStatement() val statement = expr.containingStatement()
val indexerVarPrefix = "prog8_autovar_index_" val indexerVarPrefix = "prog8_autovar_index_"
val indexerVarName: String
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
val freeVar = repo.filter { statement !in it.value }.map { it.key }.firstOrNull() // TODO make this a bit smarter so it can reuse indexer variables. BUT BEWARE of scoping+initialization problems then
if(freeVar==null) { // add another loop index var to be used for this expression
// add another loop index var to be used for this expression val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
val statementId = expr.hashCode() val indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer)
indexerVarName = "$indexerVarPrefix$statementId" repo.add(indexerVar)
// create the indexer var at block level scope // create the indexer var at block level scope
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position) null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
modifications.add(IAstModification.InsertFirst(vardecl, subroutine)) modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
var statements = repo[indexerVarName]
if(statements==null) {
statements = mutableSetOf()
repo[indexerVarName] = statements
}
statements.add(statement)
} else {
// reuse an already created indexer autovar
repo.getValue(freeVar).add(statement)
indexerVarName = freeVar
}
// assign the indexing expression to the helper variable, and replace the indexer with just the variable // replace the indexer with just the variable
// assign the indexing expression to the helper variable, but only if that hasn't been done already
val indexerExpression = expr.indexer.origExpression!! val indexerExpression = expr.indexer.origExpression!!
val target = AssignTarget(IdentifierReference(listOf(indexerVarName), indexerExpression.position), null, null, indexerExpression.position) val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
val assign = Assignment(target, indexerExpression, indexerExpression.position) val assign = Assignment(target, indexerExpression, indexerExpression.position)
val beforeWhat = expr.containingStatement() modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
modifications.add(IAstModification.InsertBefore(beforeWhat, assign, beforeWhat.definingScope()))
modifications.add(IAstModification.SetExpression( { modifications.add(IAstModification.SetExpression( {
expr.indexer.indexVar = it as IdentifierReference expr.indexer.indexVar = it as IdentifierReference
expr.indexer.indexNum = null expr.indexer.indexNum = null
@ -168,33 +267,24 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program) val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment) val targetType = assignment.target.inferType(program)
var assignments = emptyList<Assignment>()
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) { if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
assignments = if (assignment.value is ArrayLiteralValue) { if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] ' errors.err("cannot assign non-const array value, use separate assignment per field", assignment.position)
} else { } else {
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2' return copyStructValue(assignment)
} }
} }
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) { if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
assignments = if (assignment.value is ArrayLiteralValue) { if (assignment.value is ArrayLiteralValue) {
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] ' errors.err("cannot assign non-const array value, use separate assignment per element", assignment.position)
} else { } else {
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2' return copyArrayValue(assignment)
} }
} }
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
val scope = assignment.definingScope()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
modifications.add(IAstModification.Remove(assignment, scope))
return modifications
}
return noModifications return noModifications
} }
@ -247,113 +337,94 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
return noModifications return noModifications
} }
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> { private fun copyArrayValue(assign: Assignment): List<IAstModification> {
val identifier = assign.target.identifier!! val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!! val targetVar = identifier.targetVarDecl(program)!!
val alv = assign.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position) if(targetVar.arraysize==null)
} errors.err("array has no defined size", assign.position)
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val sourceIdent = assign.value as IdentifierReference val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!! val sourceVar = sourceIdent.targetVarDecl(program)!!
if(!sourceVar.isArray) { if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position) errors.err("value must be an array", sourceIdent.position)
return emptyList() } else {
if (sourceVar.arraysize!!.constIndex() != targetVar.arraysize!!.constIndex())
errors.err("element count mismatch", assign.position)
if (sourceVar.datatype != targetVar.datatype)
errors.err("element type mismatch", assign.position)
} }
val alv = sourceVar.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position) if(!errors.isEmpty())
return emptyList()
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
mutableListOf(
AddressOf(sourceIdent, assign.position),
AddressOf(identifier, assign.position),
NumericLiteralValue.optimalInteger(targetVar.arraysize!!.constIndex()!!, assign.position)
),
true,
assign.position
)
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
} }
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> { private fun copyStructValue(structAssignment: Assignment): List<IAstModification> {
if(targetVar.arraysize==null) {
errors.err("array has no defined size", identifier.position)
return emptyList()
}
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
errors.err("element count mismatch", position)
return emptyList()
}
// TODO use a pointer loop instead of individual assignments
return alv.value.withIndex().map { (index, value)->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
Assignment(AssignTarget(null, idx, null, position), value, value.position)
}
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
val identifier = structAssignment.target.identifier!! val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single() val targetVar = identifier.targetVarDecl(program)!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
val slv = structAssignment.value as? ArrayLiteralValue
if(slv==null || slv.value.size != struct.numberOfElements) {
errors.err("element count mismatch", structAssignment.position)
return emptyList()
}
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
targetDecl as VarDecl
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
sourceValue, sourceValue.position)
assign.linkParents(structAssignment)
assign
}
}
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
// TODO use memcopy beyond a certain number of elements
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!! val struct = targetVar.struct!!
when (structAssignment.value) { when (structAssignment.value) {
is IdentifierReference -> { is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!! val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program)!!
val memsize = struct.memsize(program.memsizer)
when { when {
sourceVar.struct!=null -> { sourceVar.struct!=null -> {
// struct memberwise copy // struct memberwise copy
val sourceStruct = sourceVar.struct!! val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) { if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment errors.err("struct type mismatch", structAssignment.position)
return listOf() // error will be printed elsewhere return listOf()
} }
if(struct.statements.size!=sourceStruct.statements.size) if(struct.statements.size!=sourceStruct.statements.size) {
return listOf() // error will be printed elsewhere errors.err("struct element count mismatch", structAssignment.position)
return struct.statements.zip(sourceStruct.statements).map { member -> return listOf()
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
} }
if(memsize!=sourceStruct.memsize(program.memsizer)) {
errors.err("memory size mismatch", structAssignment.position)
return listOf()
}
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
mutableListOf(
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
AddressOf(identifier, structAssignment.position),
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
),
true,
structAssignment.position
)
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
} }
sourceVar.isArray -> { sourceVar.isArray -> {
val array = (sourceVar.value as ArrayLiteralValue).value val array = sourceVar.value as ArrayLiteralValue
if(struct.statements.size!=array.size) if(struct.statements.size!=array.value.size) {
return listOf() // error will be printed elsewhere errors.err("struct element count mismatch", structAssignment.position)
return struct.statements.zip(array).map { return listOf()
val decl = it.first as VarDecl
val mangled = mangledStructMemberName(identifierName, decl.name)
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
val target = AssignTarget(targetName, null, null, structAssignment.position)
val assign = Assignment(target, it.second, structAssignment.position)
assign.linkParents(structAssignment)
assign
} }
if(memsize!=array.memsize(program.memsizer)) {
errors.err("memory size mismatch", structAssignment.position)
return listOf()
}
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
mutableListOf(
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
AddressOf(identifier, structAssignment.position),
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
),
true,
structAssignment.position
)
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
} }
else -> { else -> {
throw FatalAstException("can only assign arrays or structs to structs") throw FatalAstException("can only assign arrays or structs to structs")
@ -361,7 +432,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
} }
} }
is ArrayLiteralValue -> { is ArrayLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here") throw IllegalArgumentException("not going to do a structLv assignment here")
} }
else -> throw FatalAstException("strange struct value") else -> throw FatalAstException("strange struct value")
} }

View File

@ -1,16 +1,18 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() { class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
/* /*
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type. * Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
* (this includes function call arguments) * (this includes function call arguments)
@ -60,12 +62,15 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// see if a typecast is needed to convert the value's type into the proper target type // see if a typecast is needed to convert the value's type into the proper target type
val valueItype = assignment.value.inferType(program) val valueItype = assignment.value.inferType(program)
val targetItype = assignment.target.inferType(program, assignment) val targetItype = assignment.target.inferType(program)
if(targetItype.isKnown && valueItype.isKnown) { if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT) val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT) val valuetype = valueItype.typeOrElse(DataType.STRUCT)
if (valuetype != targettype) { if (valuetype != targettype) {
if (valuetype isAssignableTo targettype) { if (valuetype isAssignableTo targettype) {
if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
return noModifications
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
assignment.value, assignment.value,
TypecastExpression(assignment.value, targettype, true, assignment.value.position), TypecastExpression(assignment.value, targettype, true, assignment.value.position),
@ -103,43 +108,43 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
} }
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope()) return afterFunctionCallArgs(functionCallStatement)
} }
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> { override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCall, functionCall.definingScope()) return afterFunctionCallArgs(functionCall)
} }
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> { private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter's type // see if a typecast is needed to convert the arguments into the required parameter's type
val modifications = mutableListOf<IAstModification>() val modifications = mutableListOf<IAstModification>()
when(val sub = call.target.targetStatement(scope)) { when(val sub = call.target.targetStatement(program)) {
is Subroutine -> { is Subroutine -> {
for(arg in sub.parameters.zip(call.args.withIndex())) { sub.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = arg.second.value.inferType(program) val argItype = pair.second.inferType(program)
if(argItype.isKnown) { if(argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT) val argtype = argItype.typeOrElse(DataType.STRUCT)
val requiredType = arg.first.type val requiredType = pair.first.type
if (requiredType != argtype) { if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) { if (argtype isAssignableTo requiredType) {
modifications += IAstModification.ReplaceNode( modifications += IAstModification.ReplaceNode(
call.args[arg.second.index], call.args[index],
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position), TypecastExpression(pair.second, requiredType, true, pair.second.position),
call as Node) call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) { } else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead. // we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
if(arg.second.value is IdentifierReference) { if(pair.second is IdentifierReference) {
modifications += IAstModification.ReplaceNode( modifications += IAstModification.ReplaceNode(
call.args[arg.second.index], call.args[index],
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position), AddressOf(pair.second as IdentifierReference, pair.second.position),
call as Node) call as Node)
} }
} else if(arg.second.value is NumericLiteralValue) { } else if(pair.second is NumericLiteralValue) {
val cast = (arg.second.value as NumericLiteralValue).cast(requiredType) val cast = (pair.second as NumericLiteralValue).cast(requiredType)
if(cast.isValid) if(cast.isValid)
modifications += IAstModification.ReplaceNode( modifications += IAstModification.ReplaceNode(
call.args[arg.second.index], call.args[index],
cast.valueOrZero(), cast.valueOrZero(),
call as Node) call as Node)
} }
@ -149,19 +154,19 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
} }
is BuiltinFunctionStatementPlaceholder -> { is BuiltinFunctionStatementPlaceholder -> {
val func = BuiltinFunctions.getValue(sub.name) val func = BuiltinFunctions.getValue(sub.name)
for (arg in func.parameters.zip(call.args.withIndex())) { func.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = arg.second.value.inferType(program) val argItype = pair.second.inferType(program)
if (argItype.isKnown) { if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT) val argtype = argItype.typeOrElse(DataType.STRUCT)
if (arg.first.possibleDatatypes.any { argtype == it }) if (pair.first.possibleDatatypes.all { argtype != it }) {
continue for (possibleType in pair.first.possibleDatatypes) {
for (possibleType in arg.first.possibleDatatypes) { if (argtype isAssignableTo possibleType) {
if (argtype isAssignableTo possibleType) { modifications += IAstModification.ReplaceNode(
modifications += IAstModification.ReplaceNode( call.args[index],
call.args[arg.second.index], TypecastExpression(pair.second, possibleType, true, pair.second.position),
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position), call as Node)
call as Node) break
break }
} }
} }
} }

View File

@ -0,0 +1,73 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.base.Position
import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
into.statements.remove(scope)
}
}
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
return noModifications
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return before(functionCall as IFunctionCall, parent, functionCall.position)
}
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
if(functionCall.target.nameInSource==listOf("peek")) {
// peek(a) is synonymous with @(a)
val memread = DirectMemoryRead(functionCall.args.single(), position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent))
}
if(functionCall.target.nameInSource==listOf("poke")) {
// poke(a, v) is synonymous with @(a) = v
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
val assign = Assignment(tgt, functionCall.args[1], position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
}
return noModifications
}
}

View File

@ -1,25 +1,26 @@
package prog8.ast.processing package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.expressions.Expression import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilerException import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions import prog8.compiler.functions.BuiltinFunctions
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor { class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall) { override fun visit(functionCall: FunctionCall) {
val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program) val error = checkTypes(functionCall as IFunctionCall, program)
if(error!=null) if(error!=null)
throw CompilerException(error) throw CompilerException(error)
} }
override fun visit(functionCallStatement: FunctionCallStatement) { override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program) val error = checkTypes(functionCallStatement as IFunctionCall, program)
if (error!=null) if (error!=null)
throw CompilerException(error) throw CompilerException(error)
} }
@ -38,9 +39,13 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
return false return false
} }
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? { fun checkTypes(call: IFunctionCall, program: Program): String? {
val argtypes = call.args.map { it.inferType(program).typeOrElse(DataType.STRUCT) } val argITypes = call.args.map { it.inferType(program) }
val target = call.target.targetStatement(scope) val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
if(firstUnknownDt>=0)
return "argument ${firstUnknownDt+1} invalid argument type"
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(program)
if (target is Subroutine) { if (target is Subroutine) {
if(call.args.size != target.parameters.size) if(call.args.size != target.parameters.size)
return "invalid number of arguments" return "invalid number of arguments"
@ -56,8 +61,15 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
// multiple return values will NOT work inside an expression. // multiple return values will NOT work inside an expression.
// they MIGHT work in a regular assignment or just a function call statement. // they MIGHT work in a regular assignment or just a function call statement.
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
if(call !is FunctionCallStatement && parent !is Assignment && parent !is VarDecl) { if (call !is FunctionCallStatement) {
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)" val checkParent =
if(parent is TypecastExpression)
parent.parent
else
parent
if (checkParent !is Assignment && checkParent !is VarDecl) {
return "can't use subroutine call that returns multiple return values here"
}
} }
} }
} }
@ -67,12 +79,12 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
if(call.args.size != func.parameters.size) if(call.args.size != func.parameters.size)
return "invalid number of arguments" return "invalid number of arguments"
val paramtypes = func.parameters.map { it.possibleDatatypes } val paramtypes = func.parameters.map { it.possibleDatatypes }
for (x in argtypes.zip(paramtypes).withIndex()) { argtypes.zip(paramtypes).forEachIndexed { index, pair ->
val anyCompatible = x.value.second.any { argTypeCompatible(x.value.first, it) } val anyCompatible = pair.second.any { argTypeCompatible(pair.first, it) }
if (!anyCompatible) { if (!anyCompatible) {
val actual = x.value.first.toString() val actual = pair.first.toString()
val expected = x.value.second.toString() val expected = pair.second.toString()
return "argument ${x.index + 1} type mismatch, was: $actual expected: $expected" return "argument ${index + 1} type mismatch, was: $actual expected: $expected"
} }
} }
} }

View File

@ -1,5 +1,6 @@
package prog8.functions package prog8.compiler.functions
import prog8.ast.IMemSizer
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
@ -12,99 +13,142 @@ import kotlin.math.*
class FParam(val name: String, val possibleDatatypes: Set<DataType>) class FParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue
class FSignature(val pure: Boolean, // does it have side effects? class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
override fun toString(): String {
val paramConvs = params.mapIndexed { index, it ->
when {
it.reg!=null -> "$index:${it.reg}"
it.variable -> "$index:variable"
else -> "$index:???"
}
}
val returnConv =
when {
returns.reg!=null -> returns.reg.toString()
returns.floatFac1 -> "floatFAC1"
else -> "<no returnvalue>"
}
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
}
}
class FSignature(val name: String,
val pure: Boolean, // does it have side effects?
val parameters: List<FParam>, val parameters: List<FParam>,
val returntype: DataType?, val known_returntype: DataType?, // specify return type if fixed, otherwise null if it depends on the arguments
val constExpressionFunc: ConstExpressionCaller? = null) val constExpressionFunc: ConstExpressionCaller? = null) {
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
val returns = when(known_returntype) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(known_returntype, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(known_returntype, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(known_returntype, null, true)
in PassByReferenceDatatypes -> ReturnConvention(known_returntype!!, RegisterOrPair.AY, false)
else -> {
val paramType = actualParamTypes.first()
if(pure)
// return type depends on arg type
when(paramType) {
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ReturnConvention(paramType, null, true)
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
else -> ReturnConvention(paramType, null, false)
}
else {
ReturnConvention(paramType, null, false)
}
}
}
return when {
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
actualParamTypes.size==1 -> {
// one parameter? via register/registerpair
val paramConv = when(val paramType = actualParamTypes[0]) {
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
else -> ParamConvention(paramType, null, false)
}
CallConvention(listOf(paramConv), returns)
}
else -> {
// multiple parameters? via variables
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
CallConvention(paramConvs, returns)
}
}
}
}
val BuiltinFunctions = mapOf( @Suppress("UNUSED_ANONYMOUS_PARAMETER")
private val functionSignatures: List<FSignature> = listOf(
// this set of function have no return value and operate in-place: // this set of function have no return value and operate in-place:
"rol" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), FSignature("ror" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), FSignature("rol2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), FSignature("ror2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null), FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null), FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
// these few have a return value depending on the argument(s): // these few have a return value depending on the argument(s):
"max" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
"min" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof), FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
FSignature("offsetof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinOffsetof),
// normal functions follow: // normal functions follow:
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ), FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) }, FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ), FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ), FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ), FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ), FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) }, FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ), FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ), FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ), FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ), FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) }, FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) }, FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) }, FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) }, FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
"sqrt16" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } }, FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) }, FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) }, FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) }, FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"round" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) }, FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) }, FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) }, FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) }, FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) },
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) }, FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }}, FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
"msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}}, FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
"mkword" to FSignature(true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword), FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
"rnd" to FSignature(true, emptyList(), DataType.UBYTE), FSignature("peek" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UBYTE),
"rndw" to FSignature(true, emptyList(), DataType.UWORD), FSignature("peekw" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD),
"rndf" to FSignature(true, emptyList(), DataType.FLOAT), FSignature("poke" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UBYTE))), null),
"exit" to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null), FSignature("pokew" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UWORD))), null),
"rsave" to FSignature(false, emptyList(), null), FSignature("fastrnd8" , false, emptyList(), DataType.UBYTE),
"rrestore" to FSignature(false, emptyList(), null), FSignature("rnd" , false, emptyList(), DataType.UBYTE),
"set_carry" to FSignature(false, emptyList(), null), FSignature("rndw" , false, emptyList(), DataType.UWORD),
"clear_carry" to FSignature(false, emptyList(), null), FSignature("rndf" , false, emptyList(), DataType.FLOAT),
"set_irqd" to FSignature(false, emptyList(), null), FSignature("memory" , true, listOf(FParam("name", setOf(DataType.STR)), FParam("size", setOf(DataType.UWORD))), DataType.UWORD),
"clear_irqd" to FSignature(false, emptyList(), null), FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
"read_flags" to FSignature(false, emptyList(), DataType.UBYTE),
"swap" to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
"memcopy" to FSignature(false, listOf(
FParam("from", IterableDatatypes + DataType.UWORD),
FParam("to", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FSignature(false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UWORD)),
FParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FSignature(false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numwords", setOf(DataType.UWORD)),
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
"substr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("start", setOf(DataType.UBYTE)),
FParam("length", setOf(DataType.UBYTE))), null),
"leftstr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
"rightstr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
"strcmp" to FSignature(false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
) )
val BuiltinFunctions = functionSignatures.associateBy { it.name }
fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!! fun builtinMax(array: List<Number>): Number = array.maxByOrNull { it.toDouble() }!!
fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!! fun builtinMin(array: List<Number>): Number = array.minByOrNull { it.toDouble() }!!
@ -144,17 +188,17 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
} }
val func = BuiltinFunctions.getValue(function) val func = BuiltinFunctions.getValue(function)
if(func.returntype!=null) if(func.known_returntype!=null)
return InferredTypes.knownFor(func.returntype) return InferredTypes.knownFor(func.known_returntype)
// function has return values, but the return type depends on the arguments
// function has return values, but the return type depends on the arguments
return when (function) { return when (function) {
"abs" -> { "abs" -> {
val dt = args.single().inferType(program) val dt = args.single().inferType(program)
if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes) return if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
return dt dt
else else
throw FatalAstException("weird datatype passed to abs $dt") InferredTypes.InferredType.unknown()
} }
"max", "min" -> { "max", "min" -> {
when(val dt = datatypeFromIterableArg(args.single())) { when(val dt = datatypeFromIterableArg(args.single())) {
@ -229,7 +273,8 @@ private fun collectionArg(args: List<Expression>, position: Position, program: P
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position) return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
} }
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinAbs(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = float or int, result type= isSameAs as argument type // 1 arg, type = float or int, result type= isSameAs as argument type
if(args.size!=1) if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position) throw SyntaxError("abs requires one numeric argument", position)
@ -242,7 +287,29 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
} }
} }
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("offsetof requires one argument", position)
val idref = args[0] as? IdentifierReference
?: throw SyntaxError("offsetof argument should be an identifier", position)
val vardecl = idref.targetVarDecl(program)!!
val struct = vardecl.struct
if (struct == null || vardecl.datatype == DataType.STRUCT)
throw SyntaxError("offsetof can only be used on struct members", position)
val membername = idref.nameInSource.last()
var offset = 0
for(member in struct.statements) {
if((member as VarDecl).name == membername)
return NumericLiteralValue(DataType.UBYTE, offset, position)
offset += memsizer.memorySize(member.datatype)
}
throw SyntaxError("undefined struct member", position)
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte // 1 arg, type = anything, result type = ubyte
if(args.size!=1) if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position) throw SyntaxError("sizeof requires one argument", position)
@ -251,17 +318,16 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
val dt = args[0].inferType(program) val dt = args[0].inferType(program)
if(dt.isKnown) { if(dt.isKnown) {
val target = (args[0] as IdentifierReference).targetStatement(program.namespace) val target = (args[0] as IdentifierReference).targetStatement(program)
?: throw CannotEvaluateException("sizeof", "no target") ?: throw CannotEvaluateException("sizeof", "no target")
fun structSize(target: StructDecl) = fun structSize(target: StructDecl) = NumericLiteralValue(DataType.UBYTE, target.memsize(memsizer), position)
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position)
return when { return when {
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> { dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size") val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT)) val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
numericLiteral(elementDt.memorySize() * length, position) numericLiteral(memsizer.memorySize(elementDt) * length, position)
} }
dt.istype(DataType.STRUCT) -> { dt.istype(DataType.STRUCT) -> {
when (target) { when (target) {
@ -271,36 +337,20 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
} }
} }
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position) dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position) else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.STRUCT)), position)
} }
} else { } else {
throw SyntaxError("sizeof invalid argument type", position) throw SyntaxError("sizeof invalid argument type", position)
} }
} }
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
if (args.size != 1) private fun builtinLen(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
throw SyntaxError("strlen requires one argument", position)
val argument=args[0]
if(argument is StringLiteralValue)
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
val vardecl = (argument as IdentifierReference).targetVarDecl(program.namespace)
if(vardecl!=null) {
if(vardecl.datatype!=DataType.STR && vardecl.datatype!=DataType.UWORD)
throw SyntaxError("strlen must have string argument", position)
if(vardecl.autogeneratedDontRemove && vardecl.value!=null) {
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
}
}
throw NotConstArgumentException()
}
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE. // note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1) if(args.size!=1)
throw SyntaxError("len requires one argument", position) throw SyntaxError("len requires one argument", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace) val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
var arraySize = directMemVar?.arraysize?.constIndex() var arraySize = directMemVar?.arraysize?.constIndex()
if(arraySize != null) if(arraySize != null)
return NumericLiteralValue.optimalInteger(arraySize, position) return NumericLiteralValue.optimalInteger(arraySize, position)
@ -308,7 +358,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position) return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
if(args[0] !is IdentifierReference) if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier", position) throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace) val target = (args[0] as IdentifierReference).targetVarDecl(program)
?: throw CannotEvaluateException("len", "no target vardecl") ?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) { return when(target.datatype) {
@ -319,7 +369,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
NumericLiteralValue.optimalInteger(arraySize, args[0].position) NumericLiteralValue.optimalInteger(arraySize, args[0].position)
} }
DataType.STR -> { DataType.STR -> {
val refLv = target.value as StringLiteralValue val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown")
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position) NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
} }
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position) DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
@ -329,7 +379,8 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
} }
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinMkword(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 2) if (args.size != 2)
throw SyntaxError("mkword requires msb and lsb arguments", position) throw SyntaxError("mkword requires msb and lsb arguments", position)
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException() val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -338,7 +389,8 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, result, position) return NumericLiteralValue(DataType.UWORD, result, position)
} }
private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinSin8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin8 requires one argument", position) throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -346,7 +398,8 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position) return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
} }
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin8u requires one argument", position) throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -354,7 +407,8 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position) return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
} }
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinCos8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos8 requires one argument", position) throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -362,7 +416,8 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position) return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
} }
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos8u requires one argument", position) throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -370,7 +425,8 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position) return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
} }
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinSin16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin16 requires one argument", position) throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -378,7 +434,8 @@ private fun builtinSin16(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position) return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
} }
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin16u requires one argument", position) throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -386,7 +443,8 @@ private fun builtinSin16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position) return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
} }
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinCos16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos16 requires one argument", position) throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -394,7 +452,8 @@ private fun builtinCos16(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position) return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
} }
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos16u requires one argument", position) throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -402,7 +461,8 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position) return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
} }
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { @Suppress("UNUSED_PARAMETER")
private fun builtinSgn(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sgn requires one argument", position) throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()

View File

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

View File

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

View File

@ -0,0 +1,119 @@
package prog8.compiler.target
import prog8.ast.IMemSizer
import prog8.ast.IStringEncoding
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.AssignTarget
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.nio.file.Path
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun isInRegularRAM(target: AssignTarget, program: Program): Boolean {
val memAddr = target.memoryAddress
val arrayIdx = target.arrayindexed
val ident = target.identifier
when {
memAddr != null -> {
return when (memAddr.addressExpression) {
is NumericLiteralValue -> {
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
arrayIdx != null -> {
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
ident != null -> {
val decl = ident.targetVarDecl(program)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
}
}
}
internal object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun 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 -> -9999999
}
}
}
internal object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun 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 -> -9999999
}
}
}
internal fun asmGeneratorFor(
compTarget: ICompilationTarget,
program: Program,
errors: IErrorReporter,
zp: Zeropage,
options: CompilationOptions,
outputDir: Path
): IAssemblyGenerator
{
// at the moment we only have one code generation backend (for 6502 and 65c02)
return AsmGen(program, errors, zp, options, compTarget, outputDir)
}

View File

@ -1,22 +1,20 @@
package prog8.compiler.target package prog8.compiler.target
import prog8.ast.Program
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage import prog8.compiler.Zeropage
import prog8.parser.ModuleImporter
internal interface IMachineFloat { interface IMachineFloat {
fun toDouble(): Double fun toDouble(): Double
fun makeFloatFillAsm(): String fun makeFloatFillAsm(): String
} }
internal enum class CpuType { enum class CpuType {
CPU6502, CPU6502,
CPU65c02 CPU65c02
} }
internal interface IMachineDefinition { interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int val FLOAT_MEM_SIZE: Int
@ -32,8 +30,8 @@ internal interface IMachineDefinition {
fun initializeZeropage(compilerOptions: CompilationOptions) fun initializeZeropage(compilerOptions: CompilationOptions)
fun getFloat(num: Number): IMachineFloat fun getFloat(num: Number): IMachineFloat
fun getFloatRomConst(number: Double): String?
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
fun launchEmulator(programName: String) fun launchEmulator(programName: String)
fun isRegularRAMaddress(address: Int): Boolean fun isRegularRAMaddress(address: Int): Boolean
} }

View File

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

View File

@ -1,13 +1,10 @@
package prog8.compiler.target.c64 package prog8.compiler.target.c64
import prog8.ast.Program
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.CpuType import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat import prog8.compiler.target.IMachineFloat
import prog8.parser.ModuleImporter
import java.io.IOException import java.io.IOException
import java.math.RoundingMode
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.pow import kotlin.math.pow
@ -24,7 +21,6 @@ internal object C64MachineDefinition: IMachineDefinition {
override val RAW_LOAD_ADDRESS = 0xc000 override val RAW_LOAD_ADDRESS = 0xc000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
@ -32,44 +28,11 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num) override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun getFloatRomConst(number: Double): String? { override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> {
// try to match the ROM float constants to save memory return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
val mflpt5 = Mflpt5.fromNumber(number) listOf("syslib")
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4) else
when { emptyList()
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ZERO_const" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ONE_const" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "floats.FL_PIVAL"
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_N32768"
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FONE"
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRHLF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRTWO"
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_NEGHLF"
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "floats.FL_LOG2"
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "floats.FL_TENC"
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "floats.FL_NZMIL"
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FHALF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "floats.FL_LOGEB2"
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_PIHALF"
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_TWOPI"
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FR4"
else -> {
// attempt to correct for a few rounding issues
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
3.1415926536 -> return "floats.FL_PIVAL"
1.4142135624 -> return "floats.FL_SQRTWO"
0.7071067812 -> return "floats.FL_SQRHLF"
0.6931471806 -> return "floats.FL_LOG2"
else -> {}
}
}
}
return null
}
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib")
} }
override fun launchEmulator(programName: String) { override fun launchEmulator(programName: String) {
@ -110,7 +73,7 @@ internal object C64MachineDefinition: IMachineDefinition {
internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) { internal class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x02 // temp storage for a single byte override val SCRATCH_B1 = 0x02 // temp storage for a single byte
override val SCRATCH_REG = 0x03 // temp storage for a register override val SCRATCH_REG = 0x03 // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc override val SCRATCH_W1 = 0xfb // temp storage 1 for a word $fb+$fc
override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc override val SCRATCH_W2 = 0xfd // temp storage 2 for a word $fb+$fc
@ -139,7 +102,7 @@ internal object C64MachineDefinition: IMachineDefinition {
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area' // 0x90-0xfa is 'kernal work storage area'
)) ))
} }

View File

@ -172,7 +172,7 @@ object Petscii {
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK '\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK '\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE '\u2592', // ▒ 0xA6 -> MEDIUM SHADE
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
@ -236,7 +236,7 @@ object Petscii {
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK '\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK '\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE '\u2592', // ▒ 0xE6 -> MEDIUM SHADE
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
@ -431,7 +431,7 @@ object Petscii {
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK '\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK '\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE '\u2592', // ▒ 0xA6 -> MEDIUM SHADE
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
@ -495,7 +495,7 @@ object Petscii {
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK '\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK '\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE '\u2592', // ▒ 0xE6 -> MEDIUM SHADE
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
@ -626,7 +626,7 @@ object Petscii {
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK '\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK '\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0x66 -> MEDIUM SHADE '\u2592', // ▒ 0x66 -> MEDIUM SHADE
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
@ -885,7 +885,7 @@ object Petscii {
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK '\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK '\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK '\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK '_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK '\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0x66 -> MEDIUM SHADE '\u2592', // ▒ 0x66 -> MEDIUM SHADE
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK '\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK

View File

@ -1,832 +0,0 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.codegen.assignment.SourceStorageKind
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
import prog8.compiler.toHex
import prog8.functions.FSignature
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature) {
translateFunctioncall(fcall, func, false)
}
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
translateFunctioncall(fcall, func, true)
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean) {
val functionName = fcall.target.nameInSource.last()
if (discardResult) {
if (func.pure)
return // can just ignore the whole function call altogether
else if (func.returntype != null)
throw AssemblyError("discarding result of non-pure function $fcall")
}
when (functionName) {
"msb" -> funcMsb(fcall)
"lsb" -> funcLsb(fcall)
"mkword" -> funcMkword(fcall, func)
"abs" -> funcAbs(fcall, func)
"swap" -> funcSwap(fcall)
"strlen" -> funcStrlen(fcall)
"min", "max", "sum" -> funcMinMaxSum(fcall, functionName)
"any", "all" -> funcAnyAll(fcall, functionName)
"sgn" -> funcSgn(fcall, func)
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
"rdnf" -> funcVariousFloatFuncs(fcall, func, functionName)
"rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall)
"ror2" -> funcRor2(fcall)
"sort" -> funcSort(fcall)
"reverse" -> funcReverse(fcall)
"rsave" -> {
// save cpu status flag and all registers A, X, Y.
// see http://6502.org/tutorials/register_preservation.html
asmgen.out(" php | sta P8ZP_SCRATCH_REG | pha | txa | pha | tya | pha | lda P8ZP_SCRATCH_REG")
}
"rrestore" -> {
// restore all registers and cpu status flag
asmgen.out(" pla | tay | pla | tax | pla | plp")
}
"clear_carry" -> asmgen.out(" clc")
"set_carry" -> asmgen.out(" sec")
"clear_irqd" -> asmgen.out(" cli")
"set_irqd" -> asmgen.out(" sei")
else -> {
translateFunctionArguments(fcall.args, func)
asmgen.out(" jsr prog8_lib.func_$functionName")
}
}
}
private fun funcReverse(fcall: IFunctionCall) {
val variable = fcall.args.single()
if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!!
val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.constIndex()
when (decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.reverse_b
""")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.reverse_w
""")
}
DataType.ARRAY_F -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.reverse_f
""")
}
else -> throw AssemblyError("weird type")
}
}
}
private fun funcSort(fcall: IFunctionCall) {
val variable = fcall.args.single()
if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!!
val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.constIndex()
when (decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
sta P8ZP_SCRATCH_B1
""")
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
sta P8ZP_SCRATCH_B1
""")
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
}
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
else -> throw AssemblyError("weird type")
}
} else
throw AssemblyError("weird type")
}
private fun funcRor2(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror2_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
}
}
is IdentifierReference -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror2_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcRor(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" ror ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta (+) + 1
lda P8ESTACK_HI,x
sta (+) + 2
+ ror ${'$'}ffff ; modified
""")
}
}
is IdentifierReference -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.ror_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" ror $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcRol2(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol2_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
}
}
is IdentifierReference -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol2_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcRol(fcall: IFunctionCall) {
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" rol ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta (+) + 1
lda P8ESTACK_HI,x
sta (+) + 2
+ rol ${'$'}ffff ; modified
""")
}
}
is IdentifierReference -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.arrayvar)
asmgen.translateExpression(what.indexer)
asmgen.out(" jsr prog8_lib.rol_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmVariableName(what)
asmgen.out(" rol $variable | rol $variable+1")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, functionName: String) {
translateFunctionArguments(fcall.args, func)
asmgen.out(" jsr floats.func_$functionName")
}
private fun funcSgn(fcall: IFunctionCall, func: FSignature) {
translateFunctionArguments(fcall.args, func)
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> asmgen.out(" jsr math.sign_ub")
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
DataType.WORD -> asmgen.out(" jsr math.sign_w")
DataType.FLOAT -> asmgen.out(" jsr floats.sign_f")
else -> throw AssemblyError("weird type $dt")
}
}
private fun funcAnyAll(fcall: IFunctionCall, functionName: String) {
outputPushAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
private fun funcMinMaxSum(fcall: IFunctionCall, functionName: String) {
outputPushAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
private fun funcStrlen(fcall: IFunctionCall) {
val name = asmgen.asmVariableName(fcall.args[0] as IdentifierReference)
val type = fcall.args[0].inferType(program)
when {
type.istype(DataType.STR) -> asmgen.out("""
lda #<$name
ldy #>$name
jsr prog8_lib.strlen
sta P8ESTACK_LO,x
dex""")
type.istype(DataType.UWORD) -> asmgen.out("""
lda $name
ldy $name+1
jsr prog8_lib.strlen
sta P8ESTACK_LO,x
dex""")
else -> throw AssemblyError("strlen requires str or uword arg")
}
}
private fun funcSwap(fcall: IFunctionCall) {
val first = fcall.args[0]
val second = fcall.args[1]
// optimized simple case: swap two variables
if(first is IdentifierReference && second is IdentifierReference) {
val firstName = asmgen.asmVariableName(first)
val secondName = asmgen.asmVariableName(second)
val dt = first.inferType(program)
if(dt.istype(DataType.BYTE) || dt.istype(DataType.UBYTE)) {
asmgen.out(" ldy $firstName | lda $secondName | sta $firstName | sty $secondName")
return
}
if(dt.istype(DataType.WORD) || dt.istype(DataType.UWORD)) {
asmgen.out("""
ldy $firstName
lda $secondName
sta $firstName
sty $secondName
ldy $firstName+1
lda $secondName+1
sta $firstName+1
sty $secondName+1
""")
return
}
if(dt.istype(DataType.FLOAT)) {
asmgen.out("""
lda #<$firstName
sta P8ZP_SCRATCH_W1
lda #>$firstName
sta P8ZP_SCRATCH_W1+1
lda #<$secondName
sta P8ZP_SCRATCH_W2
lda #>$secondName
sta P8ZP_SCRATCH_W2+1
jsr floats.swap_floats
""")
return
}
}
// optimized simple case: swap two memory locations
if(first is DirectMemoryRead && second is DirectMemoryRead) {
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex()
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex()
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
val name2 = if(second.addressExpression is IdentifierReference) asmgen.asmVariableName(second.addressExpression as IdentifierReference) else null
when {
addr1!=null && addr2!=null -> {
asmgen.out(" ldy $addr1 | lda $addr2 | sta $addr1 | sty $addr2")
return
}
addr1!=null && name2!=null -> {
asmgen.out(" ldy $addr1 | lda $name2 | sta $addr1 | sty $name2")
return
}
name1!=null && addr2 != null -> {
asmgen.out(" ldy $name1 | lda $addr2 | sta $name1 | sty $addr2")
return
}
name1!=null && name2!=null -> {
asmgen.out(" ldy $name1 | lda $name2 | sta $name1 | sty $name2")
return
}
}
}
if(first is ArrayIndexedExpression && second is ArrayIndexedExpression) {
val arrayVarName1 = asmgen.asmVariableName(first.arrayvar)
val arrayVarName2 = asmgen.asmVariableName(second.arrayvar)
val elementDt = first.inferType(program).typeOrElse(DataType.STRUCT)
val firstNum = first.indexer.indexNum
val firstVar = first.indexer.indexVar
val secondNum = second.indexer.indexNum
val secondVar = second.indexer.indexVar
if(firstNum!=null && secondNum!=null) {
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondNum)
return
} else if(firstVar!=null && secondVar!=null) {
swapArrayValues(elementDt, arrayVarName1, firstVar, arrayVarName2, secondVar)
return
} else if(firstNum!=null && secondVar!=null) {
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondVar)
return
} else if(firstVar!=null && secondNum!=null) {
swapArrayValues(elementDt, arrayVarName1, firstVar, arrayVarName2, secondNum)
return
}
}
// all other types of swap() calls are done via the evaluation stack
fun targetFromExpr(expr: Expression, datatype: DataType): AsmAssignTarget {
return when (expr) {
is IdentifierReference -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, datatype, expr.definingSubroutine(), variableAsmName = asmgen.asmVariableName(expr))
is ArrayIndexedExpression -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, datatype, expr.definingSubroutine(), array = expr)
is DirectMemoryRead -> AsmAssignTarget(TargetStorageKind.MEMORY, program, asmgen, datatype, expr.definingSubroutine(), memory = DirectMemoryWrite(expr.addressExpression, expr.position))
else -> throw AssemblyError("invalid expression object $expr")
}
}
asmgen.translateExpression(first)
asmgen.translateExpression(second)
val datatype = first.inferType(program).typeOrElse(DataType.STRUCT)
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, datatype),
targetFromExpr(first, datatype),
false, first.position
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, datatype),
targetFromExpr(second, datatype),
false, second.position
)
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val index1 = indexValue1.number.toInt() * elementDt.memorySize()
val index2 = indexValue2.number.toInt() * elementDt.memorySize()
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
lda $arrayVarName1+$index1
ldy $arrayVarName2+$index2
sta $arrayVarName2+$index2
sty $arrayVarName1+$index1
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
lda $arrayVarName1+$index1
ldy $arrayVarName2+$index2
sta $arrayVarName2+$index2
sty $arrayVarName1+$index1
lda $arrayVarName1+$index1+1
ldy $arrayVarName2+$index2+1
sta $arrayVarName2+$index2+1
sty $arrayVarName1+$index1+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1
lda #>(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1+1
lda #<(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2
lda #>(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2+1
jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexName2: IdentifierReference) {
val idxAsmName1 = asmgen.asmVariableName(indexName1)
val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
ldx $idxAsmName1
ldy $idxAsmName2
lda $arrayVarName1,x
pha
lda $arrayVarName2,y
sta $arrayVarName1,x
pla
sta $arrayVarName2,y
ldx P8ZP_SCRATCH_REG
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
stx P8ZP_SCRATCH_REG
lda $idxAsmName1
asl a
tax
lda $idxAsmName2
asl a
tay
lda $arrayVarName1,x
pha
lda $arrayVarName2,y
sta $arrayVarName1,x
pla
sta $arrayVarName2,y
lda $arrayVarName1+1,x
pha
lda $arrayVarName2+1,y
sta $arrayVarName1+1,x
pla
sta $arrayVarName2+1,y
ldx P8ZP_SCRATCH_REG
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #>$arrayVarName1
sta P8ZP_SCRATCH_W1+1
lda $idxAsmName1
asl a
asl a
clc
adc $idxAsmName1
adc #<$arrayVarName1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ lda #>$arrayVarName2
sta P8ZP_SCRATCH_W2+1
lda $idxAsmName2
asl a
asl a
clc
adc $idxAsmName2
adc #<$arrayVarName2
sta P8ZP_SCRATCH_W2
bcc +
inc P8ZP_SCRATCH_W2+1
+ jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) {
val index1 = indexValue1.number.toInt() * elementDt.memorySize()
val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
lda $arrayVarName1 + $index1
pha
ldy $idxAsmName2
lda $arrayVarName2,y
sta $arrayVarName1 + $index1
pla
sta $arrayVarName2,y
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
lda $arrayVarName1 + $index1
pha
lda $idxAsmName2
asl a
tay
lda $arrayVarName2,y
sta $arrayVarName1 + $index1
pla
sta $arrayVarName2,y
lda $arrayVarName1 + $index1+1
pha
lda $arrayVarName2+1,y
sta $arrayVarName1 + $index1+1
pla
sta $arrayVarName2+1,y
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1
lda #>(${arrayVarName1}+$index1)
sta P8ZP_SCRATCH_W1+1
lda #>$arrayVarName1
sta P8ZP_SCRATCH_W1+1
lda $idxAsmName2
asl a
asl a
clc
adc $idxAsmName2
adc #<$arrayVarName1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val idxAsmName1 = asmgen.asmVariableName(indexName1)
val index2 = indexValue2.number.toInt() * elementDt.memorySize()
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
lda $arrayVarName2 + $index2
pha
ldy $idxAsmName1
lda $arrayVarName1,y
sta $arrayVarName2 + $index2
pla
sta $arrayVarName1,y
""")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
lda $arrayVarName2 + $index2
pha
lda $idxAsmName1
asl a
tay
lda $arrayVarName1,y
sta $arrayVarName2 + $index2
pla
sta $arrayVarName1,y
lda $arrayVarName2 + $index2+1
pha
lda $arrayVarName1+1,y
sta $arrayVarName2 + $index2+1
pla
sta $arrayVarName1+1,y
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #>$arrayVarName1
sta P8ZP_SCRATCH_W1+1
lda $idxAsmName1
asl a
asl a
clc
adc $idxAsmName1
adc #<$arrayVarName1
sta P8ZP_SCRATCH_W1
bcc +
inc P8ZP_SCRATCH_W1+1
+ lda #<(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2
lda #>(${arrayVarName2}+$index2)
sta P8ZP_SCRATCH_W2+1
jsr floats.swap_floats
""")
}
else -> throw AssemblyError("invalid aray elt type")
}
}
private fun funcAbs(fcall: IFunctionCall, func: FSignature) {
translateFunctionArguments(fcall.args, func)
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
DataType.FLOAT -> asmgen.out(" jsr floats.abs_f")
else -> throw AssemblyError("weird type")
}
}
private fun funcMkword(fcall: IFunctionCall, func: FSignature) {
// trick: push the args in reverse order (msb first, lsb second) this saves some instructions
asmgen.translateExpression(fcall.args[1])
asmgen.translateExpression(fcall.args[0])
asmgen.out(" inx | lda P8ESTACK_LO,x | sta P8ESTACK_HI+1,x")
}
private fun funcMsb(fcall: IFunctionCall) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("msb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("msb(const) should have been const-folded away")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg)
asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
} else {
asmgen.translateExpression(arg)
asmgen.out(" lda P8ESTACK_HI+1,x | sta P8ESTACK_LO+1,x")
}
}
private fun funcLsb(fcall: IFunctionCall) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("lsb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("lsb(const) should have been const-folded away")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg)
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
} else {
asmgen.translateExpression(arg)
// just ignore any high-byte
}
}
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
arg as IdentifierReference
val identifierName = asmgen.asmVariableName(arg)
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.constIndex()!!
asmgen.out("""
lda #<$identifierName
sta P8ESTACK_LO,x
lda #>$identifierName
sta P8ESTACK_HI,x
dex
lda #$size
sta P8ESTACK_LO,x
dex
""")
}
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FSignature) {
args.forEach {
asmgen.translateExpression(it)
}
}
}

View File

@ -1,278 +0,0 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.assignment.*
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
// output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values!
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val saveX = CpuRegister.X in sub.asmClobbers || sub.regXasResult() || sub.regXasParam()
if(saveX)
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine())
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, sub.parameters.withIndex().single(), stmt.args[0])
} else {
// multiple register arguments, risk of register clobbering.
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
when {
stmt.args.all {it is AddressOf ||
it is NumericLiteralValue ||
it is StringLiteralValue ||
it is ArrayLiteralValue ||
it is IdentifierReference} -> {
// no risk of clobbering for these simple argument types. Optimize the register loading.
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaRegister(sub, arg.first, arg.second)
}
}
else -> {
// Risk of clobbering due to complex expression args. Work via the stack.
registerArgsViaStackEvaluation(stmt, sub)
}
}
}
}
}
asmgen.out(" jsr $subName")
if(preserveStatusRegisterAfterCall) {
asmgen.out(" php\t; save status flags from call")
// note: the containing statement (such as the FunctionCallStatement or the Assignment or the Expression)
// must take care of popping this value again at the end!
}
if(saveX)
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
if(sub.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
asmgen.out(" inx") // align estack pointer
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
when {
argi.value.second.stack -> TODO("asmsub @stack parameter")
argi.value.second.statusflag == Statusflag.Pc -> {
require(argForCarry == null)
argForCarry = argi
}
argi.value.second.statusflag != null -> throw AssemblyError("can only use Carry as status flag parameter")
argi.value.second.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
require(argForXregister==null)
argForXregister = argi
}
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
require(argForAregister == null)
argForAregister = argi
}
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
asmgen.out(" ldy P8ESTACK_LO+${argi.index},x")
}
else -> throw AssemblyError("weird argument")
}
}
if(argForCarry!=null) {
asmgen.out("""
lda P8ESTACK_LO+${argForCarry.index},x
beq +
sec
bcs ++
+ clc
+ php""") // push the status flags
}
if(argForAregister!=null) {
when(argForAregister.value.second.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x")
RegisterOrPair.AY -> asmgen.out(" lda P8ESTACK_LO+${argForAregister.index},x | ldy P8ESTACK_HI+${argForAregister.index},x")
else -> throw AssemblyError("weird arg")
}
}
if(argForXregister!=null) {
if(argForAregister!=null)
asmgen.out(" pha")
when(argForXregister.value.second.registerOrPair) {
RegisterOrPair.X -> asmgen.out(" lda P8ESTACK_LO+${argForXregister.index},x | tax")
RegisterOrPair.AX -> asmgen.out(" ldy P8ESTACK_LO+${argForXregister.index},x | lda P8ESTACK_HI+${argForXregister.index},x | tax | tya")
RegisterOrPair.XY -> asmgen.out(" ldy P8ESTACK_HI+${argForXregister.index},x | lda P8ESTACK_LO+${argForXregister.index},x | tax")
else -> throw AssemblyError("weird arg")
}
if(argForAregister!=null)
asmgen.out(" pla")
} else {
repeat(sub.parameters.size - 1) { asmgen.out(" inx") } // unwind stack
}
if(argForCarry!=null)
asmgen.out(" plp") // set the carry flag back to correct value
}
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, parameter.value.type, sub, variableAsmName = varName)
val source = AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(tgt)
val asgn = AsmAssignment(source, tgt, false, Position.DUMMY)
asmgen.translateNormalAssignment(asgn)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("arg type unknown")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
val requiredDt = parameter.value.type
if(requiredDt!=valueDt) {
if(valueDt largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types")
}
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
if(requiredDt!=valueDt)
asmgen.signExtendStackLsb(valueDt)
}
statusflag!=null -> {
if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
lda $sourceName
beq +
sec
bcs ++
+ clc
+ pla
""")
}
else -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
pha
lda P8ESTACK_LO,x
beq +
sec
bcs ++
+ clc
+ pla
""")
}
}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
else -> {
// via register or register pair
val target = AsmAssignTarget.fromRegisters(register!!, sub, program, asmgen)
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
val scratchTarget = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UBYTE, sub, scratchVar)
val source = AsmAssignSource.fromAstSource(value, program, asmgen)
asmgen.translateNormalAssignment(AsmAssignment(source, scratchTarget, false, value.position))
asmgen.signExtendVariableLsb(scratchVar, valueDt)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, DataType.UWORD, scratchVar)
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
}
else {
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
}
}
}
}
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType in ByteDatatypes && paramType in ByteDatatypes)
return true
if(argType in WordDatatypes && paramType in WordDatatypes)
return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
if(argType==DataType.STR && paramType==DataType.UWORD)
return true
if(argType==DataType.UWORD && paramType == DataType.STR)
return true
return false
}
}

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen package prog8.compiler.target.cpu6502.codegen
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations // note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
@ -75,11 +75,11 @@ private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> { private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// the when statement (on bytes) generates a sequence of: // the when statement (on bytes) generates a sequence of:
// lda $ce01,x // lda $ce01,x
// cmp #$20 // cmp #$20
// beq check_prog8_s72choice_32 // beq check_prog8_s72choice_32
// lda $ce01,x // lda $ce01,x
// cmp #$21 // cmp #$21
// beq check_prog8_s73choice_33 // beq check_prog8_s73choice_33
// the repeated lda can be removed // the repeated lda can be removed
val mods = mutableListOf<Modification>() val mods = mutableListOf<Modification>()
@ -179,8 +179,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
} }
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> { private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// TODO not sure if this is correct in all situations....: // sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated // TODO this is not true if X is not a regular RAM memory address (but instead mapped I/O or ROM)
val mods = mutableListOf<Modification>() val mods = mutableListOf<Modification>()
for (pair in linesByFour) { for (pair in linesByFour) {
val first = pair[0].value.trimStart() val first = pair[0].value.trimStart()
@ -196,10 +196,14 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
(first.startsWith("sty ") && second.startsWith("ldy ")) || (first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx ")) (first.startsWith("stx ") && second.startsWith("ldx "))
) { ) {
val firstLoc = first.substring(4).trimStart() val third = pair[2].value.trimStart()
val secondLoc = second.substring(4).trimStart() if(!third.startsWith("b")) {
if (firstLoc == secondLoc) { // no branch instruction follows, we can potentiall remove the load instruction
mods.add(Modification(pair[1].index, true, null)) val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
}
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,14 @@
package prog8.compiler.target.c64.codegen package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.ArrayElementTypes
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.ForLoop import prog8.ast.statements.ForLoop
import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
import prog8.compiler.toHex
import kotlin.math.absoluteValue 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) {
@ -18,7 +16,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
internal fun translate(stmt: ForLoop) { internal fun translate(stmt: ForLoop) {
val iterableDt = stmt.iterable.inferType(program) val iterableDt = stmt.iterable.inferType(program)
if(!iterableDt.isKnown) if(!iterableDt.isKnown)
throw AssemblyError("can't determine iterable dt") throw AssemblyError("unknown dt")
when(stmt.iterable) { when(stmt.iterable) {
is RangeExpr -> { is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange() val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
@ -51,23 +49,17 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
val incdec = if(stepsize==1) "inc" else "dec" val incdec = if(stepsize==1) "inc" else "dec"
// loop over byte range via loopvar // loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.loopVar)
asmgen.translateExpression(range.to) asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
asmgen.translateExpression(range.from) asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.out(""" asmgen.out("""
inx lda $varname
lda P8ESTACK_LO,x $modifiedLabel cmp #0 ; modified
sta $varname beq $endLabel
lda P8ESTACK_LO+1,x $incdec $varname""")
sta $modifiedLabel+1 asmgen.jmp(loopLabel)
$loopLabel""") asmgen.out(endLabel)
asmgen.translate(stmt.body)
asmgen.out("""
lda $varname
$modifiedLabel cmp #0 ; modified
beq $endLabel
$incdec $varname
jmp $loopLabel
$endLabel inx""")
} else { } else {
@ -75,36 +67,29 @@ $endLabel inx""")
// loop over byte range via loopvar // loop over byte range via loopvar
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.loopVar)
asmgen.translateExpression(range.to) asmgen.assignExpressionToVariable(range.from, varname, ArrayElementTypes.getValue(iterableDt), null)
asmgen.translateExpression(range.from) asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayElementTypes.getValue(iterableDt), null)
asmgen.out(""" asmgen.out(loopLabel)
inx
lda P8ESTACK_LO,x
sta $varname
lda P8ESTACK_LO+1,x
sta $modifiedLabel+1
$loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.body)
asmgen.out("""
lda $varname""")
if(stepsize>0) { if(stepsize>0) {
asmgen.out(""" asmgen.out("""
clc lda $varname
adc #$stepsize clc
sta $varname adc #$stepsize
$modifiedLabel cmp #0 ; modified sta $varname
bcc $loopLabel $modifiedLabel cmp #0 ; modified
beq $loopLabel""") bmi $loopLabel
beq $loopLabel""")
} else { } else {
asmgen.out(""" asmgen.out("""
sec lda $varname
sbc #${stepsize.absoluteValue} sec
sta $varname sbc #${stepsize.absoluteValue}
$modifiedLabel cmp #0 ; modified sta $varname
bcs $loopLabel""") $modifiedLabel cmp #0 ; modified
bpl $loopLabel""")
} }
asmgen.out(""" asmgen.out(endLabel)
$endLabel inx""")
} }
} }
DataType.ARRAY_W, DataType.ARRAY_UW -> { DataType.ARRAY_W, DataType.ARRAY_UW -> {
@ -113,13 +98,11 @@ $endLabel inx""")
// words, step 1 or -1 // words, step 1 or -1
stepsize == 1 || stepsize == -1 -> { stepsize == 1 || stepsize == -1 -> {
asmgen.translateExpression(range.to)
assignLoopvar(stmt, range)
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.loopVar)
assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
lda P8ESTACK_HI+1,x sty $modifiedLabel+1
sta $modifiedLabel+1
lda P8ESTACK_LO+1,x
sta $modifiedLabel2+1 sta $modifiedLabel2+1
$loopLabel""") $loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.body)
@ -134,33 +117,28 @@ $modifiedLabel2 cmp #0 ; modified
asmgen.out(""" asmgen.out("""
+ inc $varname + inc $varname
bne $loopLabel bne $loopLabel
inc $varname+1 inc $varname+1""")
jmp $loopLabel asmgen.jmp(loopLabel)
""")
} else { } else {
asmgen.out(""" asmgen.out("""
+ lda $varname + lda $varname
bne + bne +
dec $varname+1 dec $varname+1
+ dec $varname + dec $varname""")
jmp $loopLabel""") asmgen.jmp(loopLabel)
} }
asmgen.out(endLabel) asmgen.out(endLabel)
asmgen.out(" inx")
} }
stepsize > 0 -> { stepsize > 0 -> {
// (u)words, step >= 2 // (u)words, step >= 2
asmgen.translateExpression(range.to)
asmgen.out("""
lda P8ESTACK_HI+1,x
sta $modifiedLabel+1
lda P8ESTACK_LO+1,x
sta $modifiedLabel2+1
""")
assignLoopvar(stmt, range)
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.loopVar)
asmgen.out(loopLabel) assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.body)
if (iterableDt == DataType.ARRAY_UW) { if (iterableDt == DataType.ARRAY_UW) {
@ -179,7 +157,7 @@ $modifiedLabel2 lda #0 ; modified
cmp $varname cmp $varname
bcc $endLabel bcc $endLabel
bcs $loopLabel bcs $loopLabel
$endLabel inx""") $endLabel""")
} else { } else {
asmgen.out(""" asmgen.out("""
lda $varname lda $varname
@ -196,22 +174,19 @@ $modifiedLabel lda #0 ; modified
bvc + bvc +
eor #$80 eor #$80
+ bpl $loopLabel + bpl $loopLabel
$endLabel inx""") $endLabel""")
} }
} }
else -> { else -> {
// (u)words, step <= -2 // (u)words, step <= -2
asmgen.translateExpression(range.to)
asmgen.out("""
lda P8ESTACK_HI+1,x
sta $modifiedLabel+1
lda P8ESTACK_LO+1,x
sta $modifiedLabel2+1
""")
assignLoopvar(stmt, range)
val varname = asmgen.asmVariableName(stmt.loopVar) val varname = asmgen.asmVariableName(stmt.loopVar)
asmgen.out(loopLabel) assignLoopvar(stmt, range)
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
asmgen.out("""
sty $modifiedLabel+1
sta $modifiedLabel2+1
$loopLabel""")
asmgen.translate(stmt.body) asmgen.translate(stmt.body)
if(iterableDt==DataType.ARRAY_UW) { if(iterableDt==DataType.ARRAY_UW) {
@ -229,7 +204,7 @@ $modifiedLabel cmp #0 ; modified
lda $varname lda $varname
$modifiedLabel2 cmp #0 ; modified $modifiedLabel2 cmp #0 ; modified
bcs $loopLabel bcs $loopLabel
$endLabel inx""") $endLabel""")
} else { } else {
asmgen.out(""" asmgen.out("""
lda $varname lda $varname
@ -247,7 +222,7 @@ $modifiedLabel sbc #0 ; modified
bvc + bvc +
eor #$80 eor #$80
+ bpl $loopLabel + bpl $loopLabel
$endLabel inx""") $endLabel""")
} }
} }
} }
@ -263,7 +238,7 @@ $endLabel inx""")
val endLabel = asmgen.makeLabel("for_end") val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel) asmgen.loopEndLabels.push(endLabel)
val iterableName = asmgen.asmVariableName(ident) val iterableName = asmgen.asmVariableName(ident)
val decl = ident.targetVarDecl(program.namespace)!! val decl = ident.targetVarDecl(program)!!
when(iterableDt) { when(iterableDt) {
DataType.STR -> { DataType.STR -> {
asmgen.out(""" asmgen.out("""
@ -393,7 +368,7 @@ $loopLabel""")
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt") throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
} }
2 -> { 2 -> {
if(range.last==255) { if(range.last==255 || range.last==254) {
asmgen.out(""" asmgen.out("""
inc $varname inc $varname
beq $endLabel beq $endLabel
@ -401,34 +376,34 @@ $loopLabel""")
bne $loopLabel""") bne $loopLabel""")
} else { } else {
asmgen.out(""" asmgen.out("""
inc $varname
inc $varname
lda $varname lda $varname
cmp #${range.last} cmp #${range.last+2}
beq $endLabel bne $loopLabel""")
inc $varname
inc $varname
jmp $loopLabel""")
} }
} }
-2 -> { -2 -> {
when (range.last) { when (range.last) {
0 -> asmgen.out(""" 0 -> {
lda $varname asmgen.out("""
beq $endLabel lda $varname
dec $varname beq $endLabel
dec $varname dec $varname
jmp $loopLabel""") dec $varname""")
asmgen.jmp(loopLabel)
}
1 -> asmgen.out(""" 1 -> asmgen.out("""
dec $varname dec $varname
beq $endLabel beq $endLabel
dec $varname dec $varname
bne $loopLabel""") bne $loopLabel""")
else -> asmgen.out(""" else -> asmgen.out("""
lda $varname dec $varname
cmp #${range.last} dec $varname
beq $endLabel lda $varname
dec $varname cmp #${range.last-2}
dec $varname bne $loopLabel""")
jmp $loopLabel""")
} }
} }
else -> { else -> {
@ -439,8 +414,8 @@ $loopLabel""")
beq $endLabel beq $endLabel
clc clc
adc #${range.step} adc #${range.step}
sta $varname sta $varname""")
jmp $loopLabel""") asmgen.jmp(loopLabel)
} }
} }
asmgen.out(endLabel) asmgen.out(endLabel)
@ -476,9 +451,9 @@ $loopLabel""")
sta $varname sta $varname
lda $varname+1 lda $varname+1
adc #>${range.step} adc #>${range.step}
sta $varname+1 sta $varname+1""")
jmp $loopLabel asmgen.jmp(loopLabel)
$endLabel""") asmgen.out(endLabel)
} }
} }
} }
@ -504,11 +479,10 @@ $loopLabel""")
$endLabel""") $endLabel""")
} else { } else {
asmgen.out(""" asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
inc $varname inc $varname
jmp $loopLabel lda $varname
cmp #${range.last+1}
bne $loopLabel
$endLabel""") $endLabel""")
} }
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
@ -529,23 +503,22 @@ $loopLabel""")
asmgen.out(""" asmgen.out("""
lda $varname lda $varname
beq $endLabel beq $endLabel
dec $varname dec $varname""")
jmp $loopLabel asmgen.jmp(loopLabel)
$endLabel""") asmgen.out(endLabel)
} }
1 -> { 1 -> {
asmgen.out(""" asmgen.out("""
dec $varname dec $varname
jmp $loopLabel bne $loopLabel
$endLabel""") $endLabel""")
} }
else -> { else -> {
asmgen.out(""" asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
dec $varname dec $varname
jmp $loopLabel lda $varname
cmp #${range.last-1}
bne $loopLabel
$endLabel""") $endLabel""")
} }
} }
@ -570,13 +543,12 @@ $loopLabel""")
bne + bne +
lda $varname+1 lda $varname+1
cmp #>${range.last} cmp #>${range.last}
bne +
beq $endLabel beq $endLabel
+ inc $varname + inc $varname
bne $loopLabel bne $loopLabel
inc $varname+1 inc $varname+1""")
jmp $loopLabel asmgen.jmp(loopLabel)
$endLabel""") asmgen.out(endLabel)
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
} }
@ -598,21 +570,16 @@ $loopLabel""")
bne + bne +
lda $varname+1 lda $varname+1
cmp #>${range.last} cmp #>${range.last}
bne +
beq $endLabel beq $endLabel
+ lda $varname + lda $varname
bne + bne +
dec $varname+1 dec $varname+1
+ dec $varname + dec $varname""")
jmp $loopLabel asmgen.jmp(loopLabel)
$endLabel""") asmgen.out(endLabel)
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
} }
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) { private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) =
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine(), variableAsmName=asmgen.asmVariableName(stmt.loopVar)) asmgen.assignExpressionToVariable(range.from, asmgen.asmVariableName(stmt.loopVar), stmt.loopVarDt(program).typeOrElse(DataType.STRUCT), stmt.definingSubroutine())
val src = AsmAssignSource.fromAstSource(range.from, program, asmgen).adjustSignedUnsigned(target)
val assign = AsmAssignment(src, target, false, range.position)
asmgen.translateNormalAssignment(assign)
}
} }

View File

@ -0,0 +1,347 @@
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.CpuType
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
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
saveXbeforeCall(stmt)
translateFunctionCall(stmt)
restoreXafterCall(stmt)
// just ignore any result values from the function call.
}
internal fun saveXbeforeCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
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
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
if(regSaveOnStack)
asmgen.saveRegisterStack(CpuRegister.X, keepAonEntry)
else
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine()!!)
}
}
internal fun restoreXafterCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
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
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
if(regSaveOnStack)
asmgen.restoreRegisterStack(CpuRegister.X, keepAonReturn)
else
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
internal fun translateFunctionCall(stmt: IFunctionCall) {
// Output only the code to setup the parameters and perform the actual call
// NOTE: does NOT output the code to deal with the result values!
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaVariable(sub, arg.first, arg.second)
}
} else {
// via registers
if(sub.parameters.size==1) {
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
} else {
fun isNoClobberRisk(expr: Expression): Boolean {
if(expr is AddressOf ||
expr is NumericLiteralValue ||
expr is StringLiteralValue ||
expr is ArrayLiteralValue ||
expr is IdentifierReference)
return true
if(expr is FunctionCall) {
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
return isNoClobberRisk(expr.args[0])
if(expr.target.nameInSource==listOf("mkword"))
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
}
return false
}
when {
stmt.args.all {isNoClobberRisk(it)} -> {
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
for(arg in cx16virtualRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in cpuRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in statusRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
}
else -> {
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
registerArgsViaStackEvaluation(stmt, sub)
}
}
}
}
}
if(sub.inline && asmgen.options.optimize) {
if(sub.containsDefinedVariables())
throw AssemblyError("can't inline sub with vars")
if(!sub.isAsmSubroutine && sub.parameters.isNotEmpty())
throw AssemblyError("can't inline a non-asm subroutine with parameters")
asmgen.out(" \t; inlined routine follows: ${sub.name} from ${sub.position}")
val statements = sub.statements.filter { it !is ParameterVarDecl && it !is Directive }
statements.forEach { asmgen.translate(it) }
}
else {
asmgen.out(" jsr $subName")
}
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
}
private fun registerArgsViaStackEvaluation(stmt: IFunctionCall, sub: Subroutine) {
// this is called when one or more of the arguments are 'complex' and
// cannot be assigned to a register easily or risk clobbering other registers.
if(sub.parameters.isEmpty())
return
// 1. load all arguments reversed onto the stack: first arg goes last (is on top).
for (arg in stmt.args.reversed())
asmgen.translateExpression(arg)
var argForCarry: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForXregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
var argForAregister: IndexedValue<Pair<Expression, RegisterOrStatusflag>>? = null
asmgen.out(" inx") // align estack pointer
for(argi in stmt.args.zip(sub.asmParameterRegisters).withIndex()) {
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 setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) -> {
require(argForXregister==null)
argForXregister = argi
}
argi.value.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.AY) -> {
require(argForAregister == null)
argForAregister = argi
}
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
asmgen.out(" ldy P8ESTACK_LO$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().toLowerCase()}
""")
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
else
asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
}
in WordDatatypes ->
asmgen.out("""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
lda P8ESTACK_HI$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1
""")
else -> throw AssemblyError("weird dt")
}
}
else -> throw AssemblyError("weird argument")
}
}
if(argForCarry!=null) {
val plusIdxStr = if(argForCarry.index==0) "" else "+${argForCarry.index}"
asmgen.out("""
lda P8ESTACK_LO$plusIdxStr,x
beq +
sec
bcs ++
+ clc
+ php""") // push the status flags
}
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) {
// pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedname+"."+parameter.value.name)
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub)
}
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) {
// pass argument via a register parameter
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
throw AssemblyError("unknown dt")
val valueDt = valueIDt.typeOrElse(DataType.STRUCT)
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val requiredDt = parameter.value.type
if(requiredDt!=valueDt) {
if(valueDt largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types")
}
if (statusflag!=null) {
if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
lda $sourceName
beq +
sec
bcs ++
+ clc
+ pla
""")
}
else -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
beq +
sec
bcs ++
+ clc
+""")
}
}
} else throw AssemblyError("can only use Carry as status flag parameter")
}
else {
// via register or register pair
register!!
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
asmgen.signExtendVariableLsb(scratchVar, valueDt)
asmgen.assignVariableToRegister(scratchVar, register)
} else {
val target: AsmAssignTarget =
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
else
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY))
}
}
}
private fun isArgumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType in ByteDatatypes && paramType in ByteDatatypes)
return true
if(argType in WordDatatypes && paramType in WordDatatypes)
return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
if(argType==DataType.STR && paramType==DataType.UWORD)
return true
if(argType==DataType.UWORD && paramType == DataType.STR)
return true
return false
}
}

View File

@ -1,12 +1,12 @@
package prog8.compiler.target.c64.codegen package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.PostIncrDecr import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.toHex
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -19,7 +19,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
when { when {
targetIdent!=null -> { targetIdent!=null -> {
val what = asmgen.asmVariableName(targetIdent) val what = asmgen.asmVariableName(targetIdent)
when (stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)) { when (stmt.target.inferType(program).typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what") in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> { in WordDatatypes -> {
if(incr) if(incr)
@ -54,14 +54,8 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
asmgen.out("+\tdec ${'$'}ffff\t; modified") asmgen.out("+\tdec ${'$'}ffff\t; modified")
} }
else -> { else -> {
asmgen.translateExpression(addressExpr) asmgen.assignExpressionToRegister(addressExpr, RegisterOrPair.AY)
asmgen.out(""" asmgen.out(" sta (+) + 1 | sty (+) + 2")
inx
lda P8ESTACK_LO,x
sta (+) + 1
lda P8ESTACK_HI,x
sta (+) + 2
""")
if(incr) if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified") asmgen.out("+\tinc ${'$'}ffff\t; modified")
else else
@ -73,7 +67,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar) val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT) val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
if(targetArrayIdx.indexer.indexNum!=null) { if(targetArrayIdx.indexer.indexNum!=null) {
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize() val indexValue = targetArrayIdx.indexer.constIndex()!! * program.memsizer.memorySize(elementDt)
when(elementDt) { when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue") in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> { in WordDatatypes -> {
@ -97,7 +91,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
else else
{ {
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A) asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X, false, scope) asmgen.saveRegisterLocal(CpuRegister.X, scope!!)
asmgen.out(" tax") asmgen.out(" tax")
when(elementDt) { when(elementDt) {
in ByteDatatypes -> { in ByteDatatypes -> {
@ -111,7 +105,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
lda $asmArrayvarname,x lda $asmArrayvarname,x
bne + bne +
dec $asmArrayvarname+1,x dec $asmArrayvarname+1,x
+ dec $asmArrayvarname + dec $asmArrayvarname,x
""") """)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
@ -125,7 +119,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
} }
else -> throw AssemblyError("weird array elt dt") else -> throw AssemblyError("weird array elt dt")
} }
asmgen.restoreRegister(CpuRegister.X, false) asmgen.restoreRegisterLocal(CpuRegister.X)
} }
} }
} }

View File

@ -1,14 +1,12 @@
package prog8.compiler.target.c64.codegen.assignment package prog8.compiler.target.cpu6502.codegen.assignment
import prog8.ast.IMemSizer
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget import prog8.ast.statements.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.Subroutine
import prog8.compiler.AssemblyError import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.AsmGen import prog8.compiler.target.cpu6502.codegen.AsmGen
internal enum class TargetStorageKind { internal enum class TargetStorageKind {
@ -52,13 +50,16 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
lateinit var origAssign: AsmAssignment lateinit var origAssign: AsmAssignment
init { init {
if(register!=null && datatype !in IntegerDatatypes) if(register!=null && datatype !in NumericDatatypes)
throw AssemblyError("register must be integer type") throw AssemblyError("register must be integer or float type")
} }
companion object { companion object {
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) { fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget = with(assign.target) {
val dt = inferType(program, assign).typeOrElse(DataType.STRUCT) val idt = inferType(program)
if(!idt.isKnown)
throw AssemblyError("unknown dt")
val dt = idt.typeOrElse(DataType.STRUCT)
when { when {
identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this) identifier != null -> AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, assign.definingSubroutine(), variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this) arrayindexed != null -> AsmAssignTarget(TargetStorageKind.ARRAY, program, asmgen, dt, assign.definingSubroutine(), array = arrayindexed, origAstTarget = this)
@ -75,6 +76,24 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
RegisterOrPair.AX, RegisterOrPair.AX,
RegisterOrPair.AY, RegisterOrPair.AY,
RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers) RegisterOrPair.XY -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
RegisterOrPair.FAC1,
RegisterOrPair.FAC2 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.FLOAT, scope, register = registers)
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 -> AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, DataType.UWORD, scope, register = registers)
} }
} }
} }
@ -86,7 +105,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
private val variableAsmName: String? = null, private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null, val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryRead? = null, val memory: DirectMemoryRead? = null,
val register: CpuRegister? = null, val register: RegisterOrPair? = null,
val number: NumericLiteralValue? = null, val number: NumericLiteralValue? = null,
val expression: Expression? = null val expression: Expression? = null
) )
@ -101,6 +120,14 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
asmgen.asmVariableName(array.arrayvar) asmgen.asmVariableName(array.arrayvar)
companion object { companion object {
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource {
return when {
indexer.indexNum!=null -> fromAstSource(indexer.indexNum!!, program, asmgen)
indexer.indexVar!=null -> fromAstSource(indexer.indexVar!!, program, asmgen)
else -> throw AssemblyError("weird indexer")
}
}
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource { fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
val cv = value.constValue(program) val cv = value.constValue(program)
if(cv!=null) if(cv!=null)
@ -112,7 +139,15 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation") is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> { is IdentifierReference -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT) val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = asmgen.asmVariableName(value)) val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
if(dt==DataType.UWORD && varName.toLowerCase().startsWith("cx16.r")) {
val regStr = varName.toLowerCase().substring(5)
val reg = RegisterOrPair.valueOf(regStr.toUpperCase())
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
} else {
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
}
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value) AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
@ -121,33 +156,30 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val dt = value.inferType(program).typeOrElse(DataType.STRUCT) val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value) AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
} }
else -> { is FunctionCall -> {
if(value is FunctionCall) { when (val sub = value.target.targetStatement(program)) {
// functioncall. is Subroutine -> {
val asmSub = value.target.targetStatement(program.namespace) val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
if(asmSub is Subroutine && asmSub.isAsmSubroutine) { ?: throw AssemblyError("can't translate zero return values in assignment")
when (asmSub.asmReturnvaluesRegisters.count { rr -> rr.registerOrPair!=null }) {
0 -> throw AssemblyError("can't translate zero return values in assignment") AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
1 -> { }
// assignment generation itself must make sure the status register is correct after the subroutine call, if status register is involved! is BuiltinFunctionStatementPlaceholder -> {
val reg = asmSub.asmReturnvaluesRegisters.single { rr->rr.registerOrPair!=null }.registerOrPair!! val returnType = value.inferType(program)
val dt = when(reg) { if(!returnType.isKnown)
RegisterOrPair.A, throw AssemblyError("unknown dt")
RegisterOrPair.X, AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.typeOrElse(DataType.STRUCT), expression = value)
RegisterOrPair.Y -> DataType.UBYTE }
RegisterOrPair.AX, else -> {
RegisterOrPair.AY, throw AssemblyError("weird call")
RegisterOrPair.XY -> DataType.UWORD
}
return AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value)
}
else -> throw AssemblyError("can't translate multiple return values in assignment")
}
} }
} }
}
val dt = value.inferType(program).typeOrElse(DataType.STRUCT) else -> {
return AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt, expression = value) val dt = value.inferType(program)
if(!dt.isKnown)
throw AssemblyError("unknown dt")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, dt.typeOrElse(DataType.STRUCT), expression = value)
} }
} }
} }
@ -175,12 +207,13 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
internal class AsmAssignment(val source: AsmAssignSource, internal class AsmAssignment(val source: AsmAssignSource,
val target: AsmAssignTarget, val target: AsmAssignTarget,
val isAugmentable: Boolean, val isAugmentable: Boolean,
memsizer: IMemSizer,
val position: Position) { val position: Position) {
init { init {
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY)) if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" } require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
require(source.datatype.memorySize() <= target.datatype.memorySize()) { require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
"source storage size must be less or equal to target datatype storage size" "source storage size must be less or equal to target datatype storage size"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,9 @@
package prog8.compiler.target.cx16 package prog8.compiler.target.cx16
import prog8.ast.Program
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.CpuType import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition import prog8.compiler.target.c64.C64MachineDefinition
import prog8.parser.ModuleImporter
import java.io.IOException import java.io.IOException
internal object CX16MachineDefinition: IMachineDefinition { internal object CX16MachineDefinition: IMachineDefinition {
@ -21,25 +19,23 @@ internal object CX16MachineDefinition: IMachineDefinition {
override val RAW_LOAD_ADDRESS = 0x8000 override val RAW_LOAD_ADDRESS = 0x8000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations) // the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above
override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
override lateinit var zeropage: Zeropage override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num) override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
override fun getFloatRomConst(number: Double): String? = null // Cx16 has no pulblic ROM float locations return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) { listOf("syslib")
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) else
importer.importLibraryModule(program, "syslib") emptyList()
} }
override fun launchEmulator(programName: String) { override fun launchEmulator(programName: String) {
for(emulator in listOf("x16emu")) { for(emulator in listOf("x16emu")) {
println("\nStarting Commander X16 emulator $emulator...") println("\nStarting Commander X16 emulator $emulator...")
val cmdline = listOf(emulator, "-rom", "/usr/share/x16-rom/rom.bin", "-scale", "2", val cmdline = listOf(emulator, "-scale", "2", "-run", "-prg", "$programName.prg")
"-run", "-prg", programName + ".prg")
val processb = ProcessBuilder(cmdline).inheritIO() val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process val process: Process
try { try {
@ -74,8 +70,8 @@ internal object CX16MachineDefinition: IMachineDefinition {
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) { internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x79 // temp storage for a single byte override val SCRATCH_B1 = 0x7a // temp storage for a single byte
override val SCRATCH_REG = 0x7a // temp storage for a register override val SCRATCH_REG = 0x7b // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d override val SCRATCH_W1 = 0x7c // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f override val SCRATCH_W2 = 0x7e // temp storage 2 for a word $7e+$7f

View File

@ -1,16 +1,16 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
internal class BinExprSplitter(private val program: Program) : AstWalker() { internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { // override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@ -38,22 +38,26 @@ internal class BinExprSplitter(private val program: Program) : AstWalker() {
if (binExpr != null) { if (binExpr != null) {
/* /*
reduce the complexity of a (binary) expression that has to be evaluated on the eval stack, Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
by attempting to splitting it up into individual simple steps: by attempting to splitting it up into individual simple steps.
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
X = BinExpr X = LeftExpr X = BinExpr X = LeftExpr
<operator> followed by <operator> followed by
/ \ IF 'X' not used X = BinExpr / \ IF 'X' not used X = BinExpr
/ \ IN LEFTEXPR ==> <operator> / \ IN expression ==> <operator>
/ \ / \ / \ / \
LeftExpr. RightExpr. / \ LeftExpr. RightExpr. / \
/ \ / \ X RightExpr. X RightExpr.
.. .. .. ..
*/ */
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) { if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program)) {
if (!assignment.isAugmentable) { if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
return noModifications
if(isSimpleExpression(binExpr.right) && !assignment.isAugmentable) {
val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position) val firstAssign = Assignment(assignment.target, binExpr.left, binExpr.left.position)
val targetExpr = assignment.target.toExpression() val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position) val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
@ -71,9 +75,12 @@ X = BinExpr X = LeftExpr
return noModifications return noModifications
} }
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope) = private fun isSimpleExpression(expr: Expression) =
if (target.identifier!=null || target.memoryAddress!=null || target.arrayindexed!=null) expr is IdentifierReference || expr is NumericLiteralValue || expr is AddressOf || expr is DirectMemoryRead || expr is StringLiteralValue || expr is ArrayLiteralValue || expr is RangeExpr
target.isInRegularRAM(namespace)
private fun isSimpleTarget(target: AssignTarget, program: Program) =
if (target.identifier!=null || target.memoryAddress!=null)
compTarget.isInRegularRAM(target, program)
else else
false false

View File

@ -1,26 +1,29 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.* import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.ErrorReporter
import prog8.ast.base.ParentSentinel import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.FunctionCall import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.loadAsmIncludeFile import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import java.nio.file.Path
private val alwaysKeepSubroutines = setOf( private val alwaysKeepSubroutines = setOf(
Pair("main", "start"), Pair("main", "start")
Pair("irq", "irq")
) )
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr|bra)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
class CallGraph(private val program: Program) : IAstVisitor { class CallGraph(private val program: Program, private val asmFileLoader: (filename: String, source: Path)->String) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() } val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() } val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
@ -77,7 +80,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
imports[thisModule] = imports.getValue(thisModule).plus(importedModule) imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule) importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") { } else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source) val asm = asmFileLoader(directive.args[0].str!!, thisModule.source)
val scope = directive.definingSubroutine() val scope = directive.definingSubroutine()
if(scope!=null) { if(scope!=null) {
scanAssemblyCode(asm, directive, scope) scanAssemblyCode(asm, directive, scope)
@ -89,7 +92,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
override fun visit(identifier: IdentifierReference) { override fun visit(identifier: IdentifierReference) {
// track symbol usage // track symbol usage
val target = identifier.targetStatement(this.program.namespace) val target = identifier.targetStatement(program)
if (target != null) { if (target != null) {
addNodeAndParentScopes(target) addNodeAndParentScopes(target)
} }
@ -117,19 +120,16 @@ class CallGraph(private val program: Program) : IAstVisitor {
} }
override fun visit(decl: VarDecl) { override fun visit(decl: VarDecl) {
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) { if (decl.autogeneratedDontRemove || decl.datatype==DataType.STRUCT)
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
addNodeAndParentScopes(decl) addNodeAndParentScopes(decl)
} else if(decl.parent is Block && decl.definingModule().isLibraryModule)
if (decl.datatype == DataType.STRUCT)
addNodeAndParentScopes(decl) addNodeAndParentScopes(decl)
super.visit(decl) super.visit(decl)
} }
override fun visit(functionCall: FunctionCall) { override fun visit(functionCall: FunctionCall) {
val otherSub = functionCall.target.targetSubroutine(program.namespace) val otherSub = functionCall.target.targetSubroutine(program)
if (otherSub != null) { if (otherSub != null) {
functionCall.definingSubroutine()?.let { thisSub -> functionCall.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub) calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
@ -140,7 +140,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
} }
override fun visit(functionCallStatement: FunctionCallStatement) { override fun visit(functionCallStatement: FunctionCallStatement) {
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace) val otherSub = functionCallStatement.target.targetSubroutine(program)
if (otherSub != null) { if (otherSub != null) {
functionCallStatement.definingSubroutine()?.let { thisSub -> functionCallStatement.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub) calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
@ -150,8 +150,19 @@ class CallGraph(private val program: Program) : IAstVisitor {
super.visit(functionCallStatement) super.visit(functionCallStatement)
} }
override fun visit(addressOf: AddressOf) {
val otherSub = addressOf.identifier.targetSubroutine(program)
if(otherSub!=null) {
addressOf.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(thisSub)
}
}
super.visit(addressOf)
}
override fun visit(jump: Jump) { override fun visit(jump: Jump) {
val otherSub = jump.identifier?.targetSubroutine(program.namespace) val otherSub = jump.identifier?.targetSubroutine(program)
if (otherSub != null) { if (otherSub != null) {
jump.definingSubroutine()?.let { thisSub -> jump.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub) calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
@ -167,7 +178,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
} }
override fun visit(inlineAssembly: InlineAssembly) { override fun visit(inlineAssembly: InlineAssembly) {
// parse inline asm for subroutine calls (jmp, jsr) // parse inline asm for subroutine calls (jmp, jsr, bra)
val scope = inlineAssembly.definingSubroutine() val scope = inlineAssembly.definingSubroutine()
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope) scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
super.visit(inlineAssembly) super.visit(inlineAssembly)
@ -213,7 +224,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
} }
} }
fun checkRecursiveCalls(errors: ErrorReporter) { fun checkRecursiveCalls(errors: IErrorReporter) {
val cycles = recursionCycles() val cycles = recursionCycles()
if(cycles.any()) { if(cycles.any()) {
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY) errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)

View File

@ -9,28 +9,32 @@ import kotlin.math.pow
class ConstExprEvaluator { class ConstExprEvaluator {
fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression { fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression {
return when(operator) { try {
"+" -> plus(left, right) return when(operator) {
"-" -> minus(left, right) "+" -> plus(left, right)
"*" -> multiply(left, right) "-" -> minus(left, right)
"/" -> divide(left, right) "*" -> multiply(left, right)
"%" -> remainder(left, right) "/" -> divide(left, right)
"**" -> power(left, right) "%" -> remainder(left, right)
"&" -> bitwiseand(left, right) "**" -> power(left, right)
"|" -> bitwiseor(left, right) "&" -> bitwiseand(left, right)
"^" -> bitwisexor(left, right) "|" -> bitwiseor(left, right)
"and" -> logicaland(left, right) "^" -> bitwisexor(left, right)
"or" -> logicalor(left, right) "and" -> logicaland(left, right)
"xor" -> logicalxor(left, right) "or" -> logicalor(left, right)
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position) "xor" -> logicalxor(left, right)
">" -> NumericLiteralValue.fromBoolean(left > right, left.position) "<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position) ">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position) "<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position) ">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position) "==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
"<<" -> shiftedleft(left, right) "!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
">>" -> shiftedright(left, right) "<<" -> shiftedleft(left, right)
else -> throw FatalAstException("const evaluation for invalid operator $operator") ">>" -> shiftedright(left, right)
else -> throw FatalAstException("const evaluation for invalid operator $operator")
}
} catch (ax: FatalAstException) {
throw ExpressionError(ax.message, left.position)
} }
} }

View File

@ -4,12 +4,16 @@ import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker import prog8.ast.statements.Assignment
import prog8.ast.processing.IAstModification import prog8.ast.statements.ForLoop
import prog8.ast.statements.* import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
import kotlin.math.pow
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() { internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> { override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
@ -97,21 +101,63 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftconst = expr.left.constValue(program) val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program) val rightconst = expr.right.constValue(program)
val modifications = mutableListOf<IAstModification>()
val subExpr: BinaryExpression? = when { if(expr.operator == "**" && leftconst!=null) {
leftconst!=null -> expr.right as? BinaryExpression // optimize various simple cases of ** :
rightconst!=null -> expr.left as? BinaryExpression // optimize away 1 ** x into just 1 and 0 ** x into just 0
else -> null // optimize 2 ** x into (1<<x) if both operands are integer.
val leftDt = leftconst.inferType(program).typeOrElse(DataType.STRUCT)
when (leftconst.number.toDouble()) {
0.0 -> {
val value = NumericLiteralValue(leftDt, 0, expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
}
1.0 -> {
val value = NumericLiteralValue(leftDt, 1, expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
}
2.0 -> {
if(rightconst!=null) {
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number.toDouble()), expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent)
} else {
val rightDt = expr.right.inferType(program).typeOrElse(DataType.STRUCT)
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
val targetDt =
when (parent) {
is Assignment -> parent.target.inferType(program).typeOrElse(DataType.STRUCT)
is VarDecl -> parent.datatype
else -> leftDt
}
val one = NumericLiteralValue(targetDt, 1, expr.position)
val shift = BinaryExpression(one, "<<", expr.right, expr.position)
modifications += IAstModification.ReplaceNode(expr, shift, parent)
}
}
}
}
} }
if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(program) if(expr.inferType(program).istype(DataType.FLOAT)) {
val subrightconst = subExpr.right.constValue(program) val subExpr: BinaryExpression? = when {
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) { leftconst != null -> expr.right as? BinaryExpression
// try reordering. rightconst != null -> expr.left as? BinaryExpression
val change = groupTwoConstsTogether(expr, subExpr, else -> null
}
if (subExpr != null) {
val subleftconst = subExpr.left.constValue(program)
val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst == null && subrightconst != null)) {
// try reordering.
val change = groupTwoFloatConstsTogether(
expr, subExpr,
leftconst != null, rightconst != null, leftconst != null, rightconst != null,
subleftconst != null, subrightconst != null) subleftconst != null, subrightconst != null
return change?.let { listOf(it) } ?: noModifications )
if (change != null)
modifications += change
}
} }
} }
@ -119,10 +165,10 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
if(leftconst != null && rightconst != null) { if(leftconst != null && rightconst != null) {
val evaluator = ConstExprEvaluator() val evaluator = ConstExprEvaluator()
val result = evaluator.evaluate(leftconst, expr.operator, rightconst) val result = evaluator.evaluate(leftconst, expr.operator, rightconst)
return listOf(IAstModification.ReplaceNode(expr, result, parent)) modifications += IAstModification.ReplaceNode(expr, result, parent)
} }
return noModifications return modifications
} }
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> { override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
@ -178,7 +224,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
range.step range.step
} }
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position) return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, compTarget, range.position)
} }
// adjust the datatype of a range expression in for loops to the loop variable. // adjust the datatype of a range expression in for loops to the loop variable.
@ -187,7 +233,8 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
val rangeTo = iterableRange.to as? NumericLiteralValue val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return noModifications if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!! val loopvar = forLoop.loopVar.targetVarDecl(program) ?: throw UndefinedSymbolError(forLoop.loopVar)
val stepLiteral = iterableRange.step as? NumericLiteralValue val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) { when(loopvar.datatype) {
DataType.UBYTE -> { DataType.UBYTE -> {
@ -258,13 +305,15 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
} }
} }
private fun groupTwoConstsTogether(expr: BinaryExpression, private fun groupTwoFloatConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression, subExpr: BinaryExpression,
leftIsConst: Boolean, leftIsConst: Boolean,
rightIsConst: Boolean, rightIsConst: Boolean,
subleftIsConst: Boolean, subleftIsConst: Boolean,
subrightIsConst: Boolean): IAstModification? subrightIsConst: Boolean): IAstModification?
{ {
// NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS
// todo: this implements only a small set of possible reorderings at this time // todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) { if(expr.operator==subExpr.operator) {
// both operators are the same. // both operators are the same.

View File

@ -4,27 +4,33 @@ import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.ArrayIndex import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.ForLoop import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.compiler.target.CompilationTarget import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
// Fix up the literal value's type to match that of the vardecl // Fix up the literal value's type to match that of the vardecl
internal class VarConstantValueTypeAdjuster(private val program: Program) : AstWalker() { internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declConstValue = decl.value?.constValue(program) try {
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST) val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& !declConstValue.inferType(program).istype(decl.datatype)) { && !declConstValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable // cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype) val cast = declConstValue.cast(decl.datatype)
if(cast.isValid) if(cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl)) return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
} }
return noModifications return noModifications
} }
} }
@ -33,7 +39,7 @@ internal class VarConstantValueTypeAdjuster(private val program: Program) : AstW
// Replace all constant identifiers with their actual value, // Replace all constant identifiers with their actual value,
// and the array var initializer values and sizes. // and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those. // This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() { internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> { override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
@ -47,11 +53,22 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(forloop!=null && identifier===forloop.loopVar) if(forloop!=null && identifier===forloop.loopVar)
return noModifications return noModifications
val cval = identifier.constValue(program) ?: return noModifications try {
return when (cval.type) { val cval = identifier.constValue(program) ?: return noModifications
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent)) return when (cval.type) {
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant") in NumericDatatypes -> listOf(
else -> noModifications IAstModification.ReplaceNode(
identifier,
NumericLiteralValue(cval.type, cval.number, identifier.position),
identifier.parent
)
)
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> noModifications
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
return noModifications
} }
} }
@ -78,11 +95,16 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
} }
} else if(arraysize.constIndex()==null) { } else if(arraysize.constIndex()==null) {
// see if we can calculate the size from other fields // see if we can calculate the size from other fields
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program) try {
if(cval!=null) { val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
arraysize.indexVar = null if (cval != null) {
arraysize.origExpression = null arraysize.indexVar = null
arraysize.indexNum = cval arraysize.origExpression = null
arraysize.indexNum = cval
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
return noModifications
} }
} }
} }
@ -170,7 +192,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr==null && litval!=null) { if(rangeExpr==null && litval!=null) {
// arraysize initializer is a single int, and we know the size. // arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble() val fillvalue = litval.number.toDouble()
if (fillvalue < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE) if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", litval.position) errors.err("float value overflow", litval.position)
else { else {
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.

View File

@ -2,10 +2,14 @@ package prog8.optimizer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.IntegerDatatypes
import prog8.ast.base.NumericDatatypes
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker import prog8.ast.statements.Assignment
import prog8.ast.processing.IAstModification import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.pow import kotlin.math.pow
@ -98,6 +102,12 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (leftVal != null && expr.operator in associativeOperators && rightVal == null) if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
return listOf(IAstModification.SwapOperands(expr)) return listOf(IAstModification.SwapOperands(expr))
// NonBinaryExpression <associativeoperator> BinaryExpression --> BinaryExpression <associativeoperator> NonBinaryExpression
if (expr.operator in associativeOperators && expr.left !is BinaryExpression && expr.right is BinaryExpression) {
if(parent !is Assignment || !(expr.left isSameAs parent.target))
return listOf(IAstModification.SwapOperands(expr))
}
// X + (-A) --> X - A // X + (-A) --> X - A
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") { if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
@ -338,7 +348,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (leftVal == null && rightVal == null) if (leftVal == null && rightVal == null)
return null return null
val (expr2, _, rightVal2) = reorderAssociative(expr, leftVal) val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal)
if (rightVal2 != null) { if (rightVal2 != null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal2 val rightConst: NumericLiteralValue = rightVal2
@ -477,7 +487,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
when (expr.operator) { when (expr.operator) {
"%" -> { "%" -> {
if (cv == 1.0) { if (cv == 1.0) {
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position) val idt = expr.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
return NumericLiteralValue(idt.typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) { } else if (cv == 2.0) {
expr.operator = "&" expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(1, expr.position) expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
@ -559,7 +572,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (leftVal == null && rightVal == null) if (leftVal == null && rightVal == null)
return null return null
val (expr2, _, rightVal2) = reorderAssociative(expr, leftVal) val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal)
if (rightVal2 != null) { if (rightVal2 != null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val leftValue: Expression = expr2.left val leftValue: Expression = expr2.left
@ -606,8 +619,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (amount == 0) { if (amount == 0) {
return expr.left return expr.left
} }
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT) val targetIDt = expr.left.inferType(program)
when (targetDt) { if(!targetIDt.isKnown)
throw FatalAstException("unknown dt")
when (val targetDt = targetIDt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) { if (amount >= 8) {
return NumericLiteralValue(targetDt, 0, expr.position) return NumericLiteralValue(targetDt, 0, expr.position)
@ -617,8 +632,7 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (amount >= 16) { if (amount >= 16) {
return NumericLiteralValue(targetDt, 0, expr.position) return NumericLiteralValue(targetDt, 0, expr.position)
} else if (amount >= 8) { } else if (amount >= 8) {
// TODO is this correct??? val lsb = FunctionCall(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
val lsb = TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
if (amount == 8) { if (amount == 8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position) return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
} }
@ -640,7 +654,10 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
if (amount == 0) { if (amount == 0) {
return expr.left return expr.left
} }
when (expr.left.inferType(program).typeOrElse(DataType.STRUCT)) { val idt = expr.left.inferType(program)
if(!idt.isKnown)
throw FatalAstException("unknown dt")
when (idt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> { DataType.UBYTE -> {
if (amount >= 8) { if (amount >= 8) {
return NumericLiteralValue.optimalInteger(0, expr.position) return NumericLiteralValue.optimalInteger(0, expr.position)
@ -655,12 +672,15 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
DataType.UWORD -> { DataType.UWORD -> {
if (amount >= 16) { if (amount >= 16) {
return NumericLiteralValue.optimalInteger(0, expr.position) return NumericLiteralValue.optimalInteger(0, expr.position)
} else if (amount >= 8) { }
else if (amount >= 8) {
val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position) val msb = FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) { if (amount == 8) {
return TypecastExpression(msb, DataType.UWORD, true, expr.position) // mkword(0, msb(v))
val zero = NumericLiteralValue(DataType.UBYTE, 0, expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
} }
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position) return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
} }
} }
DataType.WORD -> { DataType.WORD -> {
@ -675,17 +695,17 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
return null return null
} }
private fun reorderAssociative(expr: BinaryExpression, leftVal: NumericLiteralValue?): ReorderedAssociativeBinaryExpr { private fun reorderAssociativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteralValue?): BinExprWithConstants {
if (expr.operator in associativeOperators && leftVal != null) { if (expr.operator in associativeOperators && leftVal != null) {
// swap left and right so that right is always the constant // swap left and right so that right is always the constant
val tmp = expr.left val tmp = expr.left
expr.left = expr.right expr.left = expr.right
expr.right = tmp expr.right = tmp
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(program), leftVal) return BinExprWithConstants(expr, expr.right.constValue(program), leftVal)
} }
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program)) return BinExprWithConstants(expr, leftVal, expr.right.constValue(program))
} }
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?) private data class BinExprWithConstants(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
} }

View File

@ -1,16 +1,19 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.ErrorReporter import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal fun Program.constantFold(errors: ErrorReporter) { internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
val valuetypefixer = VarConstantValueTypeAdjuster(this) val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this) valuetypefixer.visit(this)
if(errors.isEmpty()) { if(errors.isEmpty()) {
valuetypefixer.applyModifications() valuetypefixer.applyModifications()
val replacer = ConstantIdentifierReplacer(this, errors) val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
replacer.visit(this) replacer.visit(this)
if (errors.isEmpty()) { if (errors.isEmpty()) {
replacer.applyModifications() replacer.applyModifications()
@ -19,7 +22,7 @@ internal fun Program.constantFold(errors: ErrorReporter) {
if(errors.isEmpty()) { if(errors.isEmpty()) {
valuetypefixer.applyModifications() valuetypefixer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this) val optimizer = ConstantFoldingOptimizer(this, compTarget)
optimizer.visit(this) optimizer.visit(this)
while (errors.isEmpty() && optimizer.applyModifications() > 0) { while (errors.isEmpty() && optimizer.applyModifications() > 0) {
optimizer.visit(this) optimizer.visit(this)
@ -38,8 +41,11 @@ internal fun Program.constantFold(errors: ErrorReporter) {
} }
internal fun Program.optimizeStatements(errors: ErrorReporter): Int { internal fun Program.optimizeStatements(errors: IErrorReporter,
val optimizer = StatementOptimizer(this, errors) functions: IBuiltinFunctions,
compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget, asmFileLoader)
optimizer.visit(this) optimizer.visit(this)
val optimizationCount = optimizer.applyModifications() val optimizationCount = optimizer.applyModifications()
@ -54,8 +60,8 @@ internal fun Program.simplifyExpressions() : Int {
return opti.applyModifications() return opti.applyModifications()
} }
internal fun Program.splitBinaryExpressions() : Int { internal fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int {
val opti = BinExprSplitter(this) val opti = BinExprSplitter(this, compTarget)
opti.visit(this) opti.visit(this)
return opti.applyModifications() return opti.applyModifications()
} }

View File

@ -1,30 +1,36 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget import prog8.ast.walk.AstWalker
import prog8.functions.BuiltinFunctions import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
import kotlin.math.floor import kotlin.math.floor
internal class StatementOptimizer(private val program: Program, internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter) : AstWalker() { private val errors: IErrorReporter,
private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String
) : AstWalker() {
private val noModifications = emptyList<IAstModification>() private val noModifications = emptyList<IAstModification>()
private val callgraph = CallGraph(program) private val callgraph = CallGraph(program, asmFileLoader)
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
override fun after(block: Block, parent: Node): Iterable<IAstModification> { override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) { if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) { if (block.containsNoCodeNorVars()) {
errors.warn("removing empty block '${block.name}'", block.position) if(block.name != program.internedStringsModuleName)
errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope)) return listOf(IAstModification.Remove(block, parent as INameScope))
} }
@ -39,7 +45,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in subroutine.definingBlock().options() val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null && !forceOutput) { if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.containsNoCodeNorVars()) { if(subroutine.containsNoCodeNorVars() && !subroutine.inline) {
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position) errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = callgraph.calledBy.getValue(subroutine).map { val removals = callgraph.calledBy.getValue(subroutine).map {
IAstModification.Remove(it, it.definingScope()) IAstModification.Remove(it, it.definingScope())
@ -50,7 +56,8 @@ internal class StatementOptimizer(private val program: Program,
} }
if(subroutine !in callgraph.usedSymbols && !forceOutput) { if(subroutine !in callgraph.usedSymbols && !forceOutput) {
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position) if(!subroutine.isAsmSubroutine)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope())) return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
} }
@ -70,60 +77,58 @@ internal class StatementOptimizer(private val program: Program,
} }
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) { if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
val functionName = functionCallStatement.target.nameInSource[0] val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) { if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position) errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope())) return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
} }
} }
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters // printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
// this is a C-64 specific optimization if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print")) {
val arg = functionCallStatement.args.single() val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference? val stringVar: IdentifierReference? = if(arg is AddressOf) {
stringVar = if(arg is AddressOf) {
arg.identifier arg.identifier
} else { } else {
arg as? IdentifierReference arg as? IdentifierReference
} }
if(stringVar!=null) { if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program.namespace)!! val vardecl = stringVar.targetVarDecl(program)!!
val string = vardecl.value as? StringLiteralValue val string = vardecl.value as? StringLiteralValue
if(string!=null) { if(string!=null) {
val pos = functionCallStatement.position val pos = functionCallStatement.position
if (string.value.length == 1) { if (string.value.length == 1) {
val firstCharEncoded = CompilationTarget.instance.encodeString(string.value, string.altEncoding)[0] val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement( val chrout = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos), IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)), mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos functionCallStatement.void, pos
) )
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent)) return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if (string.value.length == 2) { } else if (string.value.length == 2) {
val firstTwoCharsEncoded = CompilationTarget.instance.encodeString(string.value.take(2), string.altEncoding) val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement( val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos), IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)), mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos functionCallStatement.void, pos
) )
val chrout2 = FunctionCallStatement( val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos), IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)), mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos functionCallStatement.void, pos
) )
val anonscope = AnonymousScope(mutableListOf(), pos) return listOf(
anonscope.statements.add(chrout1) IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
anonscope.statements.add(chrout2) IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent)) )
} }
} }
} }
} }
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter // if the first instruction in the called subroutine is a return statement, remove the jump altogeter
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace) val subroutine = functionCallStatement.target.targetSubroutine(program)
if(subroutine!=null) { if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return) if(first is Return)
@ -135,7 +140,7 @@ internal class StatementOptimizer(private val program: Program,
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> { override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value // if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program.namespace) val subroutine = functionCall.target.targetSubroutine(program)
if(subroutine!=null) { if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value!=null) { if(first is Return && first.value!=null) {
@ -203,14 +208,14 @@ internal class StatementOptimizer(private val program: Program,
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
} }
} }
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program.namespace) val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program)
if(iterable!=null) { if(iterable!=null) {
if(iterable.datatype==DataType.STR) { if(iterable.datatype==DataType.STR) {
val sv = iterable.value as StringLiteralValue val sv = iterable.value as StringLiteralValue
val size = sv.value.length val size = sv.value.length
if(size==1) { if(size==1) {
// loop over string of length 1 -> just assign the single character // loop over string of length 1 -> just assign the single character
val character = CompilationTarget.instance.encodeString(sv.value, sv.altEncoding)[0] val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position) val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position) val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position)) scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
@ -291,22 +296,10 @@ internal class StatementOptimizer(private val program: Program,
return noModifications return noModifications
} }
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
// remove empty choices
class ChoiceRemover(val choice: WhenChoice) : IAstModification {
override fun perform() {
whenStatement.choices.remove(choice)
}
}
return whenStatement.choices
.filter { !it.statements.containsCodeOrVars() }
.map { ChoiceRemover(it) }
}
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> { override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump // if the jump is to the next statement, remove the jump
val scope = jump.definingScope() val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope) val label = jump.identifier?.targetStatement(program)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, jump.definingScope())) return listOf(IAstModification.Remove(jump, jump.definingScope()))
@ -323,7 +316,7 @@ internal class StatementOptimizer(private val program: Program,
val op1 = binExpr.operator val op1 = binExpr.operator
val op2 = rExpr.operator val op2 = rExpr.operator
if(rExpr.left is NumericLiteralValue && op2 in setOf("+", "*", "&", "|")) { if(rExpr.left is NumericLiteralValue && op2 in associativeOperators) {
// associative operator, make sure the constant numeric value is second (right) // associative operator, make sure the constant numeric value is second (right)
return listOf(IAstModification.SwapOperands(rExpr)) return listOf(IAstModification.SwapOperands(rExpr))
} }
@ -377,7 +370,7 @@ internal class StatementOptimizer(private val program: Program,
return listOf(IAstModification.Remove(assignment, assignment.definingScope())) return listOf(IAstModification.Remove(assignment, assignment.definingScope()))
} }
val targetIDt = assignment.target.inferType(program, assignment) val targetIDt = assignment.target.inferType(program)
if(!targetIDt.isKnown) if(!targetIDt.isKnown)
throw FatalAstException("can't infer type of assignment target") throw FatalAstException("can't infer type of assignment target")
@ -390,7 +383,7 @@ internal class StatementOptimizer(private val program: Program,
// assignments of the form: X = X <operator> <expr> // assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0) // remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions // optimize/rewrite some other expressions
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
when (bexpr.operator) { when (bexpr.operator) {
"+" -> { "+" -> {
if (rightCv == 0.0) { if (rightCv == 0.0) {
@ -441,6 +434,47 @@ internal class StatementOptimizer(private val program: Program,
return noModifications return noModifications
} }
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
fun returnViaIntermediary(value: Expression): Iterable<IAstModification>? {
val returnDt = returnStmt.definingSubroutine()!!.returntypes.single()
if (returnDt in IntegerDatatypes) {
// first assign to intermediary, then return that register
val returnValueIntermediary =
when(returnDt) {
DataType.UBYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_ub"), returnStmt.position)
DataType.BYTE -> IdentifierReference(listOf("prog8_lib", "retval_interm_b"), returnStmt.position)
DataType.UWORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_uw"), returnStmt.position)
DataType.WORD -> IdentifierReference(listOf("prog8_lib", "retval_interm_w"), returnStmt.position)
else -> throw FatalAstException("weird return dt")
}
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
val assign = Assignment(tgt, value, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary, returnStmt.position)
return listOf(
IAstModification.InsertBefore(returnStmt, assign, parent as INameScope),
IAstModification.ReplaceNode(returnStmt, returnReplacement, parent)
)
}
return null
}
when(returnStmt.value) {
is PrefixExpression -> {
val mod = returnViaIntermediary(returnStmt.value!!)
if(mod!=null)
return mod
}
is BinaryExpression -> {
val mod = returnViaIntermediary(returnStmt.value!!)
if(mod!=null)
return mod
}
else -> {}
}
return super.after(returnStmt, parent)
}
private fun hasBreak(scope: INameScope): Boolean { private fun hasBreak(scope: INameScope): Boolean {
class Searcher: IAstVisitor class Searcher: IAstVisitor

View File

@ -3,23 +3,33 @@ package prog8.optimizer
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.ErrorReporter import prog8.ast.expressions.BinaryExpression
import prog8.ast.processing.AstWalker import prog8.ast.expressions.FunctionCall
import prog8.ast.processing.IAstModification import prog8.ast.expressions.PrefixExpression
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal class UnusedCodeRemover(private val program: Program, private val errors: ErrorReporter): AstWalker() { internal class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget,
private val asmFileLoader: (filename: String, source: Path)->String): AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> { override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program) val callgraph = CallGraph(program, asmFileLoader)
val removals = mutableListOf<IAstModification>() val removals = mutableListOf<IAstModification>()
// remove all subroutines that aren't called, or are empty // remove all subroutines that aren't called, or are empty
val entrypoint = program.entrypoint() val entrypoint = program.entrypoint()
program.modules.forEach { program.modules.forEach {
callgraph.forAllSubroutines(it) { sub -> callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) { val forceOutput = "force_output" in sub.definingBlock().options()
if (sub !== entrypoint && !forceOutput && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
removals.add(IAstModification.Remove(sub, sub.definingScope())) removals.add(IAstModification.Remove(sub, sub.definingScope()))
} }
} }
@ -91,9 +101,16 @@ internal class UnusedCodeRemover(private val program: Program, private val error
val assign1 = stmtPairs[0] as? Assignment val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null && !assign2.isAugmentable) { if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace)) { if (assign1.target.isSameAs(assign2.target, program) && compTarget.isInRegularRAM(assign1.target, program)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray()))) if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
linesToRemove.add(assign1) // only remove the second assignment if its value is a simple expression!
when(assign2.value) {
is PrefixExpression,
is BinaryExpression,
is TypecastExpression,
is FunctionCall -> { /* don't remove */ }
else -> linesToRemove.add(assign1)
}
} }
} }
} }

View File

@ -1,22 +0,0 @@
package prog8.server.dbus
//import org.freedesktop.dbus.interfaces.DBusInterface
//
//
//interface IrmenDbusTest: DBusInterface
//{
// fun Status(address: String): Map<Int, String>
//}
//
//
//internal class TestService: IrmenDbusTest {
// override fun Status(address: String): Map<Int, String> {
// return mapOf(
// 5 to "hello",
// 42 to address
// )
// }
//
// override fun isRemote() = true
// override fun getObjectPath() = "/razorvine/TestService"
//}

View File

@ -1,17 +0,0 @@
package prog8.server.dbus
//import org.freedesktop.dbus.connections.impl.DBusConnection
//
//
//fun main() {
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
// println(it.names.toList())
// println(it.uniqueName)
// println(it.address)
// println(it.machineId)
// val obj = it.getRemoteObject("local.net.razorvine.dbus.test", "/razorvine/TestService", IrmenDbusTest::class.java)
// println(obj.Status("irmen"))
// }
//}
//

View File

@ -1,18 +0,0 @@
package prog8.server.dbus
//import org.freedesktop.dbus.connections.impl.DBusConnection
//
//
//fun main() {
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
// it.requestBusName("local.net.razorvine.dbus.test")
// println(it.names.toList())
// println(it.uniqueName)
// println(it.address)
// println(it.machineId)
// val service = TestService()
// it.exportObject(service.objectPath, service)
//
// Thread.sleep(100000)
// }
//}

View File

@ -5,19 +5,22 @@ import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.Module import prog8.ast.*
import prog8.ast.Program import prog8.ast.base.DataType
import prog8.ast.base.* import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.C64Target import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5 import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
import java.io.CharConversionException import java.io.CharConversionException
import java.nio.file.Path import java.nio.file.Path
import kotlin.test.* import kotlin.test.*
@ -52,8 +55,8 @@ class TestCompiler {
assertEquals("-\$c382", (-50050).toHex()) assertEquals("-\$c382", (-50050).toHex())
assertEquals("-\$ffff", (-65535).toHex()) assertEquals("-\$ffff", (-65535).toHex())
assertEquals("-\$ffff", (-65535L).toHex()) assertEquals("-\$ffff", (-65535L).toHex())
assertFailsWith<CompilerException> { 65536.toHex() } assertFailsWith<IllegalArgumentException> { 65536.toHex() }
assertFailsWith<CompilerException> { 65536L.toHex() } assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
} }
@Test @Test
@ -132,7 +135,7 @@ class TestC64Zeropage {
@Test @Test
fun testNames() { fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
zp.allocate("", DataType.UBYTE, null, errors) zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, errors) zp.allocate("", DataType.UBYTE, null, errors)
@ -145,37 +148,37 @@ class TestC64Zeropage {
@Test @Test
fun testZpFloatEnable() { fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors) zp.allocate("", DataType.FLOAT, null, errors)
} }
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors) zp2.allocate("", DataType.FLOAT, null, errors)
} }
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false)) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
zp3.allocate("", DataType.FLOAT, null, errors) zp3.allocate("", DataType.FLOAT, null, errors)
} }
@Test @Test
fun testZpModesWithFloats() { fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
} }
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false)) C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
} }
} }
@Test @Test
fun testZpDontuse() { fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
println(zp.free) println(zp.free)
assertEquals(0, zp.available()) assertEquals(0, zp.available())
assertFailsWith<CompilerException> { assertFailsWith<CompilerException> {
@ -185,19 +188,19 @@ class TestC64Zeropage {
@Test @Test
fun testFreeSpaces() { fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false)) val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp1.available()) assertEquals(18, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(89, zp2.available()) assertEquals(89, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false)) val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(125, zp3.available()) assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false)) val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp4.available()) assertEquals(238, zp4.available())
} }
@Test @Test
fun testReservedSpace() { fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false)) val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp1.available()) assertEquals(238, zp1.available())
assertTrue(50 in zp1.free) assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free) assertTrue(100 in zp1.free)
@ -206,7 +209,7 @@ class TestC64Zeropage {
assertTrue(200 in zp1.free) assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free) assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free) assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false)) val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
assertEquals(139, zp2.available()) assertEquals(139, zp2.available())
assertFalse(50 in zp2.free) assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free) assertFalse(100 in zp2.free)
@ -219,7 +222,7 @@ class TestC64Zeropage {
@Test @Test
fun testBasicsafeAllocation() { fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available()) assertEquals(18, zp.available())
assertFailsWith<ZeropageDepletedError> { assertFailsWith<ZeropageDepletedError> {
@ -242,7 +245,7 @@ class TestC64Zeropage {
@Test @Test
fun testFullAllocation() { fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp.available()) assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.UWORD, null, errors) val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3) assertTrue(loc > 3)
@ -272,7 +275,7 @@ class TestC64Zeropage {
@Test @Test
fun testEfficientAllocation() { fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false)) val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available()) assertEquals(18, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors)) assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors)) assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
@ -288,6 +291,43 @@ class TestC64Zeropage {
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors)) assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0, zp.available()) assertEquals(0, zp.available())
} }
@Test
fun testReservedLocations() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCx16Zeropage {
@Test
fun testReservedLocations() {
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
@Test
fun testFreeSpaces() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(88, zp1.available())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(175, zp3.available())
val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp1.available())
assertTrue(0x22 in zp1.free)
assertTrue(0x80 in zp1.free)
assertTrue(0xff in zp1.free)
assertFalse(0x02 in zp1.free)
assertFalse(0x21 in zp1.free)
}
} }
@ -387,74 +427,77 @@ class TestPetscii {
class TestMemory { class TestMemory {
private class DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
private class DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
@Test @Test
fun testInValidRamC64_memory_addresses() { fun testInValidRamC64_memory_addresses() {
CompilationTarget.instance = C64Target
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY) var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var scope = AnonymousScope(mutableListOf(), Position.DUMMY) val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(target.isInRegularRAM(scope)) assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertTrue(C64Target.isInRegularRAM(target, program))
assertTrue(target.isInRegularRAM(scope))
} }
@Test @Test
fun testNotInValidRamC64_memory_addresses() { fun testNotInValidRamC64_memory_addresses() {
CompilationTarget.instance = C64Target
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY) var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var scope = AnonymousScope(mutableListOf(), Position.DUMMY) val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(target.isInRegularRAM(scope)) assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAM(scope))
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY) memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY) assertFalse(C64Target.isInRegularRAM(target, program))
assertFalse(target.isInRegularRAM(scope))
} }
@Test @Test
fun testInValidRamC64_memory_identifiers() { fun testInValidRamC64_memory_identifiers() {
CompilationTarget.instance = C64Target
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR) var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
assertTrue(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR) target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
assertFalse(target.isInRegularRAM(target.definingScope())) assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST) target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
assertTrue(target.isInRegularRAM(target.definingScope())) assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST) target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
assertFalse(target.isInRegularRAM(target.definingScope())) assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY) target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
assertFalse(target.isInRegularRAM(target.definingScope())) assertFalse(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
@ -463,90 +506,96 @@ class TestMemory {
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY) val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
module.linkParents(ParentSentinel)
return target return target
} }
@Test @Test
fun testInValidRamC64_memory_expression() { fun testInValidRamC64_memory_expression() {
CompilationTarget.instance = C64Target
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY) val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY) val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val scope = AnonymousScope(mutableListOf(), Position.DUMMY) val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(target.isInRegularRAM(scope)) assertFalse(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testInValidRamC64_variable() { fun testInValidRamC64_variable() {
CompilationTarget.instance = C64Target
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertTrue(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testInValidRamC64_memmap_variable() { fun testInValidRamC64_memmap_variable() {
CompilationTarget.instance = C64Target
val address = 0x1000 val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertTrue(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testNotInValidRamC64_memmap_variable() { fun testNotInValidRamC64_memmap_variable() {
CompilationTarget.instance = C64Target
val address = 0xd020 val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY) val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertFalse(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testInValidRamC64_array() { fun testInValidRamC64_array() {
CompilationTarget.instance = C64Target
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertTrue(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testInValidRamC64_array_memmapped() { fun testInValidRamC64_array_memmapped() {
CompilationTarget.instance = C64Target
val address = 0x1000 val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertTrue(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
} }
@Test @Test
fun testNotValidRamC64_array_memmapped() { fun testNotValidRamC64_array_memmapped() {
CompilationTarget.instance = C64Target
val address = 0xe000 val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY) val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY) val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY) val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY) val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY) val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel) val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
assertFalse(target.isInRegularRAM(target.definingScope())) val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
} }
} }

67
compilerAst/build.gradle Normal file
View File

@ -0,0 +1,67 @@
plugins {
id 'antlr'
id 'java'
id "org.jetbrains.kotlin.jvm" version "1.4.30"
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenCentral()
}
configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar
compile {
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
}
}
dependencies {
antlr 'org.antlr:antlr4:4.9'
implementation 'org.antlr:antlr4-runtime:4.9'
implementation project(':parser')
// antlr('org.antlr:antlr4:4.9') {
// exclude group: 'com.ibm.icu', module: 'icu4j'
// }
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
}

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" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="antlr-runtime-4.9" level="project" />
</component>
</module>

View File

@ -5,9 +5,9 @@ import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.toHex import prog8.ast.walk.IAstVisitor
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor { class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
private var scopelevel = 0 private var scopelevel = 0
@ -143,7 +143,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) { for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
val reg = val reg =
when { when {
param.second.stack -> "stack"
param.second.registerOrPair!=null -> param.second.registerOrPair.toString() param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
param.second.statusflag!=null -> param.second.statusflag.toString() param.second.statusflag!=null -> param.second.statusflag.toString()
else -> "?????" else -> "?????"
@ -173,8 +172,19 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(") ") output(") ")
} }
if(subroutine.returntypes.any()) { if(subroutine.returntypes.any()) {
val rt = subroutine.returntypes.single() if(subroutine.asmReturnvaluesRegisters.isNotEmpty()) {
output("-> ${datatypeString(rt)} ") val rts = subroutine.returntypes.zip(subroutine.asmReturnvaluesRegisters).joinToString(", ") {
val dtstr = datatypeString(it.first)
if(it.second.registerOrPair!=null)
"$dtstr @${it.second.registerOrPair}"
else
"$dtstr @${it.second.statusflag}"
}
output("-> $rts ")
} else {
val rts = subroutine.returntypes.joinToString(", ") { datatypeString(it) }
output("-> $rts ")
}
} }
if(subroutine.asmAddress!=null) if(subroutine.asmAddress!=null)
outputln("= ${subroutine.asmAddress.toHex()}") outputln("= ${subroutine.asmAddress.toHex()}")
@ -403,10 +413,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
outputlni("}}") outputlni("}}")
} }
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
output(builtinFunctionStatementPlaceholder.name)
}
override fun visit(whenStatement: WhenStatement) { override fun visit(whenStatement: WhenStatement) {
output("when ") output("when ")
whenStatement.condition.accept(this) whenStatement.condition.accept(this)

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