Compare commits

...

159 Commits
v5.4 ... v6.0

Author SHA1 Message Date
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
165 changed files with 15446 additions and 3743 deletions

2
.gitignore vendored
View File

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

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">
<library name="antlr-4.8-complete">
<library name="antlr-4.9-complete">
<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>
<JAVADOC />
<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">
<library name="antlr-runtime-4.8">
<library name="antlr-runtime-4.9">
<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>
<JAVADOC />
<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">
<library name="kotlinx-cli-jvm-0.1.0-dev-5">
<library name="kotlinx-cli-jvm">
<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>
<JAVADOC />
<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/modules.xml generated
View File

@ -3,8 +3,10 @@
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" filepath="$PROJECT_DIR$/httpCompilerService/httpCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
</modules>
</component>

View File

@ -33,11 +33,13 @@ What does Prog8 provide?
- 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
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
- structs to group together sets of variables and manipulate them at once
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- fast execution speed due to compilation to native assembly code
- inline assembly allows you to have full control when every cycle or byte matters
- supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, and provides them also on the C64.
*Rapid edit-compile-run-debug cycle:*
@ -48,8 +50,8 @@ What does Prog8 provide?
*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), the main target.
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU) .
- "c64": Commodore-64 (6510 CPU = almost a 6502)
- "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)!
@ -84,9 +86,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
ubyte candidate_prime = 2 ; is increased in the loop
sub start() {
; clear the sieve, to reset starting situation on subsequent runs
memset(sieve, 256, false)
; calculate primes
sys.memset(sieve, 256, false) ; clear the sieve
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0
repeat {
@ -97,17 +97,17 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
txt.print(", ")
amount++
}
txt.chrout('\n')
txt.nl()
txt.print("number of primes (expected 54): ")
txt.print_ub(amount)
txt.chrout('\n')
txt.nl()
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
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.
@ -124,6 +124,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
when compiled an ran on a C-64 you'll get:
![c64 screen](docs/source/_static/primes_example.png)
@ -140,7 +141,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)
The CommanderX16 compiler target is quite capable already too, here's a well known space ship
animated in 3D with hidden line removal, in the CommanderX16 emulator:
There are a couple of examples specially made for the CommanderX16 compiler target.
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)

View File

@ -1,28 +1,18 @@
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20"
}
}
plugins {
// id "org.jetbrains.kotlin.jvm" version "1.4.20"
id 'application'
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.2.0'
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm" version "1.4.21"
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '6.1.0'
}
apply plugin: "kotlin"
apply plugin: "java"
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven { url "https://dl.bintray.com/orangy/maven/" }
maven { url "https://kotlin.bintray.com/kotlinx" }
}
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
@ -32,9 +22,9 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.antlr:antlr4-runtime:4.8'
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
// implementation 'net.razorvine:ksim65:1.6'
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
// implementation 'net.razorvine:ksim65:1.8'
// implementation "com.github.hypfvieh:dbus-java:3.2.4"
implementation project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
@ -76,7 +66,8 @@ sourceSets {
startScripts.enabled = true
application {
mainClassName = 'prog8.CompilerMainKt'
mainClass = 'prog8.CompilerMainKt'
mainClassName = 'prog8.CompilerMainKt' // deprecated
applicationName = 'p8compile'
}
@ -112,5 +103,5 @@ dokka {
}
task wrapper(type: Wrapper) {
gradleVersion = '6.1.1'
gradleVersion = '6.7'
}

View File

@ -13,7 +13,7 @@
<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="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
<orderEntry type="library" name="antlr-runtime-4.8" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
<orderEntry type="library" name="antlr-runtime-4.9" level="project" />
</component>
</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_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

View File

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

View File

@ -175,8 +175,8 @@ func_log2_fac1 .proc
stx P8ZP_SCRATCH_REG
jsr LOG
jsr MOVEF
lda #<FL_LOG2
ldy #>FL_LOG2
lda #<FL_LOG2_const
ldy #>FL_LOG2_const
jsr MOVFM
jsr FDIVT
ldx P8ZP_SCRATCH_REG

View File

@ -27,23 +27,36 @@ graphics {
}
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)
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
; Bresenham algorithm.
; This code special cases various quadrant loops to allow simple ++ and -- operations.
; TODO rewrite this in optimized assembly
; This code special-cases various quadrant loops to allow simple ++ and -- operations.
if y1>y2 {
; make sure dy is always positive to avoid 8 instead of just 4 special cases
; make sure dy is always positive to have only 4 instead of 8 special cases
swap(x1, x2)
swap(y1, y2)
}
word @zp d = 0
ubyte positive_ix = true
word @zp dx = x2-x1 as word
word @zp dy = y2-y1
if dx==0 {
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 {
dx = -dx
positive_ix = false
@ -108,74 +121,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) {
; Midpoint algorithm
if radius==0
return
ubyte @zp ploty
ubyte @zp xx = radius
ubyte @zp yy = 0
byte @zp decisionOver2 = 1-xx as byte
word @zp decisionOver2 = (1 as word)-radius
while xx>=yy {
internal_plotx = xcenter + xx
while radius>=yy {
internal_plotx = xcenter + radius
ploty = ycenter + yy
internal_plot(ploty)
internal_plotx = xcenter - xx
internal_plotx = xcenter - radius
internal_plot(ploty)
internal_plotx = xcenter + xx
internal_plotx = xcenter + radius
ploty = ycenter - yy
internal_plot(ploty)
internal_plotx = xcenter - xx
internal_plotx = xcenter - radius
internal_plot(ploty)
internal_plotx = xcenter + yy
ploty = ycenter + xx
ploty = ycenter + radius
internal_plot(ploty)
internal_plotx = xcenter - yy
internal_plot(ploty)
internal_plotx = xcenter + yy
ploty = ycenter - xx
ploty = ycenter - radius
internal_plot(ploty)
internal_plotx = xcenter - yy
internal_plot(ploty)
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
decisionOver2 += (yy as word)*2+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
radius--
decisionOver2 += (yy as word -radius)*2+1
}
}
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; Midpoint algorithm, filled
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
while xx>=yy {
ubyte ycenter_plus_yy = ycenter + yy
ubyte ycenter_min_yy = ycenter - yy
ubyte ycenter_plus_xx = ycenter + xx
ubyte ycenter_min_xx = ycenter - xx
internal_plotx = xcenter-xx
repeat xx*2+1 {
internal_plot(ycenter_plus_yy)
internal_plot(ycenter_min_yy)
internal_plotx++
}
internal_plotx = xcenter-yy
repeat yy*2+1 {
internal_plot(ycenter_plus_xx)
internal_plot(ycenter_min_xx)
internal_plotx++
}
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
decisionOver2 += (yy as word)*2+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
radius--
decisionOver2 += (yy as word -radius)*2+1
}
}
}
@ -188,11 +300,11 @@ graphics {
; @(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 {{
stx internal_plotx
sty internal_plotx+1
jmp internal_plot
stx graphics.internal_plotx
sty graphics.internal_plotx+1
jsr graphics.internal_plot
}}
}
@ -244,6 +356,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

@ -225,6 +225,41 @@ romsub $FFF3 = IOBASE() -> uword @ XY ; read base addr
; ---- 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: ----
@ -259,33 +294,7 @@ asmsub init_system() {
}}
}
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
%asm {{
sei
lda #14
sta $01 ; bank the kernal in
jmp (c64.RESET_VEC)
}}
}
sub wait(uword jiffies) {
uword current_time = 0
c64.SETTIM(0,0,0)
while current_time < jiffies {
; read clock
%asm {{
stx P8ZP_SCRATCH_REG
jsr c64.RDTIM
sta current_time
stx current_time+1
ldx P8ZP_SCRATCH_REG
}}
}
}
asmsub disable_runstop_and_charsetswitch() {
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
sta 657 ; disable charset switching
@ -379,7 +388,7 @@ IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irqvec() {
asmsub restore_irqvec() clobbers(A) {
%asm {{
sei
lda #<c64.IRQDFRT
@ -460,5 +469,214 @@ _raster_irq_handler
; ---- 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

@ -19,6 +19,19 @@ sub clear_screen() {
txt.chrout(147)
}
sub home() {
txt.chrout(19)
}
sub nl() {
txt.chrout('\n')
}
sub spc() {
txt.chrout(' ')
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
; (assumes screen and color matrix are at their default addresses)

View File

@ -1,30 +1,28 @@
; Prog8 definitions for number conversions routines.
; Number conversions routines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
conv {
; ----- number conversions to decimal strings
; ----- number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
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
%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
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 {{
%asm {{
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
@ -193,11 +191,7 @@ decOnes .byte 0
}}
}
; ----- utility functions ----
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{
@ -210,7 +204,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)
%asm {{
stx P8ZP_SCRATCH_REG
@ -232,7 +226,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)
%asm {{
sta P8ZP_SCRATCH_REG
@ -245,49 +239,95 @@ asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
sta output+2
sty output+3
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
; ---- 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)
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
}}
}
inline asmsub str2ubyte(str string @AY) clobbers(Y) -> ubyte @A {
; -- returns in A 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)
; result in A, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
jmp str2uword
jsr conv.str2uword
}}
}
asmsub str2byte(str string @ AY) clobbers(Y) -> ubyte @A {
; -- returns the signed byte value of the string number argument in AY
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 {{
jmp str2word
jsr conv.str2word
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY {
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_W2
sta _mod+1
sty _mod+2
_result = P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty _result
sty _result+1
_mod lda $ffff,y ; modified
sty cx16.r15+1
_loop
lda (P8ZP_SCRATCH_W2),y
sec
sbc #48
bpl +
_done ; return result
bpl _digit
_done
sty cx16.r15
lda _result
ldy _result+1
rts
+ cmp #10
_digit
cmp #10
bcs _done
; add digit to result
pha
@ -299,7 +339,7 @@ _done ; return result
bcc +
inc _result+1
+ iny
bne _mod
bne _loop
; never reached
_result_times_10 ; (W*4 + W)*2
@ -322,19 +362,21 @@ _result_times_10 ; (W*4 + W)*2
}}
}
asmsub str2word(str string @ AY) -> word @ AY {
asmsub str2word(str string @AY) -> word @AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
%asm {{
_result = P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_result = P8ZP_SCRATCH_W1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty _result
sty _result+1
sty _negative
lda (P8ZP_SCRATCH_W1),y
sty cx16.r15+1
lda (P8ZP_SCRATCH_W2),y
cmp #'+'
bne +
iny
@ -342,11 +384,12 @@ _result = P8ZP_SCRATCH_W2
bne _parse
inc _negative
iny
_parse lda (P8ZP_SCRATCH_W1),y
_parse lda (P8ZP_SCRATCH_W2),y
sec
sbc #48
bpl _digit
_done ; return result
_done
sty cx16.r15
lda _negative
beq +
sec
@ -359,7 +402,8 @@ _done ; return result
+ lda _result
ldy _result+1
rts
_digit cmp #10
_digit
cmp #10
bcs _done
; add digit to result
pha
@ -377,77 +421,103 @@ _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 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.
%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
}}
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
}}
}
}

View File

@ -1,87 +0,0 @@
%target cx16
c64colors {
uword[] 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[] 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[] 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
]
ubyte color
sub set_palette_pepto() {
for color in 0 to 15 {
uword cc = colorpalette_pepto[color]
cx16.vpoke(1, $fa00 + color*2, lsb(cc)) ; G, B
cx16.vpoke(1, $fa01 + color*2, msb(cc)) ; R
}
}
sub set_palette_light() {
for color in 0 to 15 {
uword cc = colorpalette_light[color]
cx16.vpoke(1, $fa00 + color*2, lsb(cc)) ; G, B
cx16.vpoke(1, $fa01 + color*2, msb(cc)) ; R
}
}
sub set_palette_dark() {
for color in 0 to 15 {
uword cc = colorpalette_dark[color]
cx16.vpoke(1, $fa00 + color*2, lsb(cc)) ; G, B
cx16.vpoke(1, $fa01 + color*2, msb(cc)) ; R
}
}
}

View File

@ -99,9 +99,9 @@ _flt65536 .byte 145,0,0,0,0 ; 65536.0
asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- signed 16 bit word in A/Y (lo/hi) to float in fac1
%asm {{
sta P8ZP_SCRATCH_W2
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_B1
jmp GIVAYF ; this uses the inverse order, Y/A
}}
}
@ -110,9 +110,9 @@ asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
; ---- fac1 to signed word in A/Y
%asm {{
jsr FTOSWORDYA ; note the inverse Y/A order
sta P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_REG
ldy P8ZP_SCRATCH_B1
rts
}}
}

View File

@ -0,0 +1,756 @@
%target cx16
; Bitmap pixel graphics module for the CommanderX16
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
; This only works on the Cx16. No text layer is currently shown, text can be drawn as part of the bitmap itself.
; Note: for compatible graphics code that words on C64 too, use the "graphics" module instead.
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
; 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 = 255
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) {
; mode 0 = bitmap 320 x 240 x 1c monochrome
; mode 1 = bitmap 320 x 240 x 256c
; mode 128 = bitmap 640 x 480 x 1c monochrome
; ...other modes?
; copy the lower-case charset to the upper part of the vram, so we can use it later to plot text
when mode {
0 -> {
; 320 x 240 x 1c
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
}
1 -> {
; 320 x 240 x 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
}
128 -> {
; 640 x 480 x 1c
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
}
255 -> {
; 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
}
}
active_mode = mode
if bpp
clear_screen()
}
sub clear_screen() {
monochrome_stipple(false)
position(0, 0)
when active_mode {
0 -> {
; 320 x 240 x 1c
repeat 240/2/8
cs_innerloop640()
}
1 -> {
; 320 x 240 x 256c
repeat 240/2
cs_innerloop640()
}
128 -> {
; 640 x 480 x 1c
repeat 480/8
cs_innerloop640()
}
}
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 -> {
; 8bpp mode
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
}}
}
0, 128 -> {
; 1 bpp mode
ubyte separate_pixels = (8-lsb(x)) & 7
if separate_pixels as uword > length
separate_pixels = lsb(length)
repeat separate_pixels {
; this could be optimized by setting this byte in 1 go but probably not worth it due to code size
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 {
; this could be optimized by setting this byte in 1 go but probably not worth it due to code size
plot(x, y, color)
x++
}
}
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) ; vera auto-increment off again
}
}
}
sub vertical_line(uword x, uword y, uword height, ubyte color) {
position(x,y)
if active_mode==1 {
; 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 -
+
}}
return
}
; 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.
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) ; no auto advance
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if active_mode>=128
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
%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
; 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
}}
}
}
}
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.
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]
uword addr
ubyte value
when active_mode {
0 -> {
%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)
}
}
}
128 -> {
%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)
}
}
}
1 -> {
void addr_mul_320_add_24(y, x) ; 24 bits result is in r0 and r1L
value = lsb(cx16.r1)
cx16.vpoke(value, 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
}
}
}
sub position(uword @zp x, uword y) {
when active_mode {
0 -> {
cx16.r0 = y*(320/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
128 -> {
cx16.r0 = y*(640/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
1 -> {
void addr_mul_320_add_24(y, x) ; 24 bits result is in r0 and r1L
ubyte 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).
%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).
%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 !
uword chardataptr
when active_mode {
0, 128 -> {
; 1-bitplane modes
cx16.r2 = 40
if active_mode>=128
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++
}
}
1 -> {
; 320 x 240 x 256c
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
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++
}
}
}
}
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_320_add_24(uword address @R0, uword value @AY) clobbers(A) -> uword @R0, ubyte @R1 {
%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

@ -2,9 +2,12 @@
%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.
; only black/white monchrome 320x200 for now.
; only black/white monchrome 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 {
const uword WIDTH = 320
@ -13,8 +16,7 @@ graphics {
sub enable_bitmap_mode() {
; enable bitmap screen, erase it and set colors to black/white.
void cx16.screen_set_mode($80)
cx16.r0 = 0
cx16.GRAPH_init()
cx16.GRAPH_init(0)
clear_screen(1, 0)
}
@ -31,11 +33,25 @@ graphics {
}
sub line(uword @zp x1, ubyte @zp y1, uword @zp x2, ubyte @zp y2) {
cx16.r0 = x1
cx16.r1 = y1
cx16.r2 = x2
cx16.r3 = y2
cx16.GRAPH_draw_line()
cx16.GRAPH_draw_line(x1, y1, x2, y2)
}
sub fillrect(uword x, uword y, uword width, uword height) {
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) {
@ -46,107 +62,77 @@ graphics {
;cx16.GRAPH_draw_oval(false) ; currently this call is not implemented on cx16, does a BRK
; Midpoint algorithm
if radius==0
return
ubyte @zp xx = radius
ubyte @zp yy = 0
byte @zp decisionOver2 = 1-xx as byte
word @zp decisionOver2 = (1 as word)-xx
while xx>=yy {
cx16.r0 = xcenter + xx
cx16.r1 = ycenter + yy
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - xx
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter + xx
cx16.r1 = ycenter - yy
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - xx
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter + xx
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - yy
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter + yy
cx16.r1 = ycenter - xx
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
cx16.r0 = xcenter - yy
cx16.FB_cursor_position()
cx16.FB_cursor_position2()
cx16.FB_set_pixel(1)
yy++
if decisionOver2<=0 {
decisionOver2 += 2*yy+1
decisionOver2 += (yy as word)*2+1
} else {
xx--
decisionOver2 += 2*(yy-xx)+1
decisionOver2 += (yy as word -xx)*2+1
}
}
}
sub disc(uword xcenter, ubyte ycenter, ubyte radius) {
; cx16.r0 = xcenter - radius/2
; cx16.r1 = ycenter - radius/2
; cx16.r2 = radius*2
; cx16.r3 = radius*2
; cx16.GRAPH_draw_oval(true) ; currently this call is not implemented on cx16, does a BRK
ubyte xx = radius
ubyte yy = 0
byte decisionOver2 = 1-xx as byte
while xx>=yy {
ubyte ycenter_plus_yy = ycenter + yy
ubyte ycenter_min_yy = ycenter - yy
ubyte ycenter_plus_xx = ycenter + xx
ubyte ycenter_min_xx = ycenter - xx
uword @zp plotx
cx16.r0 = xcenter-xx
cx16.r1 = ycenter_plus_yy
cx16.FB_cursor_position()
repeat xx*2+1
cx16.FB_set_pixel(1)
cx16.r0 = xcenter-xx
cx16.r1 = ycenter_min_yy
cx16.FB_cursor_position()
repeat xx*2+1
cx16.FB_set_pixel(1)
cx16.r0 = xcenter-yy
cx16.r1 = ycenter_plus_xx
cx16.FB_cursor_position()
repeat yy*2+1
cx16.FB_set_pixel(1)
cx16.r0 = xcenter-yy
cx16.r1 = ycenter_min_xx
cx16.FB_cursor_position()
repeat yy*2+1
cx16.FB_set_pixel(1)
if radius==0
return
ubyte @zp yy = 0
word decisionOver2 = (1 as word)-radius
while radius>=yy {
horizontal_line(xcenter-radius, ycenter+yy, radius*2+1)
horizontal_line(xcenter-radius, ycenter-yy, radius*2+1)
horizontal_line(xcenter-yy, ycenter+radius, yy*2+1)
horizontal_line(xcenter-yy, ycenter-radius, yy*2+1)
yy++
if decisionOver2<=0
decisionOver2 += 2*yy+1
decisionOver2 += (yy as word)*2+1
else {
xx--
decisionOver2 += 2*(yy-xx)+1
radius--
decisionOver2 += (yy as word -radius)*2+1
}
}
}
sub plot(uword plotx, ubyte ploty) {
cx16.r0 = plotx
cx16.r1 = ploty
cx16.FB_cursor_position()
cx16.FB_set_pixel(1)
inline asmsub plot(uword plotx @R0, uword ploty @R1) clobbers(A, X, Y) {
%asm {{
jsr cx16.FB_cursor_position
lda #1
jsr cx16.FB_set_pixel
}}
}
}

View File

@ -0,0 +1,171 @@
%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) {
cx16.vpoke(1, $fa00+index*2, lsb(color))
cx16.vpoke(1, $fa01+index*2, msb(color))
}
sub set_rgb4(uword palletteptr, 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, @(palletteptr))
palletteptr++
cx16.vpoke(1, vera_palette_ptr, @(palletteptr))
palletteptr++
vera_palette_ptr+=2
}
}
sub set_rgb8(uword palletteptr, 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 = @(palletteptr) >> 4
palletteptr++
greenblue = @(palletteptr) & %11110000
palletteptr++
greenblue |= @(palletteptr) >> 4 ; add Blue
palletteptr++
cx16.vpoke(1, vera_palette_ptr, greenblue)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, red)
vera_palette_ptr++
}
}
sub set_monochrome() {
vera_palette_ptr = $fa00
cx16.vpoke(1, vera_palette_ptr, 0)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, 0)
vera_palette_ptr++
repeat 255 {
cx16.vpoke(1, vera_palette_ptr, 255)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, 255)
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 $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
romsub $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 $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
@ -56,6 +56,48 @@ 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 $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 {
@ -86,50 +128,50 @@ cx16 {
; VERA registers
const uword VERA_BASE = $9F20
&ubyte VERA_ADDR_L = VERA_BASE + $0000
&ubyte VERA_ADDR_M = VERA_BASE + $0001
&ubyte VERA_ADDR_H = VERA_BASE + $0002
&ubyte VERA_DATA0 = VERA_BASE + $0003
&ubyte VERA_DATA1 = VERA_BASE + $0004
&ubyte VERA_CTRL = VERA_BASE + $0005
&ubyte VERA_IEN = VERA_BASE + $0006
&ubyte VERA_ISR = VERA_BASE + $0007
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
const uword VERA_BASE = $9F20
&ubyte VERA_ADDR_L = VERA_BASE + $0000
&ubyte VERA_ADDR_M = VERA_BASE + $0001
&ubyte VERA_ADDR_H = VERA_BASE + $0002
&ubyte VERA_DATA0 = VERA_BASE + $0003
&ubyte VERA_DATA1 = VERA_BASE + $0004
&ubyte VERA_CTRL = VERA_BASE + $0005
&ubyte VERA_IEN = VERA_BASE + $0006
&ubyte VERA_ISR = VERA_BASE + $0007
&ubyte VERA_IRQ_LINE_L = VERA_BASE + $0008
&ubyte VERA_DC_VIDEO = VERA_BASE + $0009
&ubyte VERA_DC_HSCALE = VERA_BASE + $000A
&ubyte VERA_DC_VSCALE = VERA_BASE + $000B
&ubyte VERA_DC_BORDER = VERA_BASE + $000C
&ubyte VERA_DC_HSTART = VERA_BASE + $0009
&ubyte VERA_DC_HSTOP = VERA_BASE + $000A
&ubyte VERA_DC_VSTART = VERA_BASE + $000B
&ubyte VERA_DC_VSTOP = VERA_BASE + $000C
&ubyte VERA_L0_CONFIG = VERA_BASE + $000D
&ubyte VERA_L0_MAPBASE = VERA_BASE + $000E
&ubyte VERA_L0_TILEBASE = VERA_BASE + $000F
&ubyte VERA_L0_HSCROLL_L = VERA_BASE + $0010
&ubyte VERA_L0_HSCROLL_H = VERA_BASE + $0011
&ubyte VERA_L0_VSCROLL_L = VERA_BASE + $0012
&ubyte VERA_L0_VSCROLL_H = VERA_BASE + $0013
&ubyte VERA_L1_CONFIG = VERA_BASE + $0014
&ubyte VERA_L1_MAPBASE = VERA_BASE + $0015
&ubyte VERA_L1_TILEBASE = VERA_BASE + $0016
&ubyte VERA_L1_HSCROLL_L = VERA_BASE + $0017
&ubyte VERA_L1_HSCROLL_H = VERA_BASE + $0018
&ubyte VERA_L1_VSCROLL_L = VERA_BASE + $0019
&ubyte VERA_L1_VSCROLL_H = VERA_BASE + $001A
&ubyte VERA_AUDIO_CTRL = VERA_BASE + $001B
&ubyte VERA_AUDIO_RATE = VERA_BASE + $001C
&ubyte VERA_AUDIO_DATA = VERA_BASE + $001D
&ubyte VERA_SPI_DATA = VERA_BASE + $001E
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
; VERA_PSG_BASE = $1F9C0
; VERA_PALETTE_BASE = $1FA00
; VERA_SPRITES_BASE = $1FC00
; I/O
const uword via1 = $9f60 ;VIA 6522 #1
const uword via1 = $9f60 ;VIA 6522 #1
&ubyte d1prb = via1+0
&ubyte d1pra = via1+1
&ubyte d1ddrb = via1+2
@ -147,23 +189,23 @@ cx16 {
&ubyte d1ier = via1+14
&ubyte d1ora = via1+15
const uword via2 = $9f70 ;VIA 6522 #2
&ubyte d2prb =via2+0
&ubyte d2pra =via2+1
&ubyte d2ddrb =via2+2
&ubyte d2ddra =via2+3
&ubyte d2t1l =via2+4
&ubyte d2t1h =via2+5
&ubyte d2t1ll =via2+6
&ubyte d2t1lh =via2+7
&ubyte d2t2l =via2+8
&ubyte d2t2h =via2+9
&ubyte d2sr =via2+10
&ubyte d2acr =via2+11
&ubyte d2pcr =via2+12
&ubyte d2ifr =via2+13
&ubyte d2ier =via2+14
&ubyte d2ora =via2+15
const uword via2 = $9f70 ;VIA 6522 #2
&ubyte d2prb = via2+0
&ubyte d2pra = via2+1
&ubyte d2ddrb = via2+2
&ubyte d2ddra = via2+3
&ubyte d2t1l = via2+4
&ubyte d2t1h = via2+5
&ubyte d2t1ll = via2+6
&ubyte d2t1lh = via2+7
&ubyte d2t2l = via2+8
&ubyte d2t2h = via2+9
&ubyte d2sr = via2+10
&ubyte d2acr = via2+11
&ubyte d2pcr = via2+12
&ubyte d2ifr = via2+13
&ubyte d2ier = via2+14
&ubyte d2ora = via2+15
; ---- Commander X-16 additions on top of C64 kernal routines ----
@ -190,88 +232,187 @@ romsub $ff6b = mouse_get(ubyte zpdataptr @X) clobbers(A)
romsub $ff71 = mouse_scan() clobbers(A, X, Y)
romsub $ff53 = joystick_scan() clobbers(A, X, Y)
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff4d = clock_set_date_time() clobbers(A, X, Y) ; args: r0, r1, r2, r3L
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) ; outout args: r0, r1, r2, r3L
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) -> 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
; 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 $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 $ff2c = GRAPH_draw_line() clobbers(A,X,Y) ; uses x1=r0, y1=r1, x2=r2, y2=r3
romsub $ff2f = GRAPH_draw_rect(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3, cornerradius=r4
romsub $ff32 = GRAPH_move_rect() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, width=r4, height=r5
romsub $ff35 = GRAPH_draw_oval(ubyte fill @Pc) clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $ff38 = GRAPH_draw_image() clobbers(A,X,Y) ; uses x=r0, y=r1, ptr=r2, width=r3, height=r4
romsub $ff3b = GRAPH_set_font() clobbers(A,X,Y) ; uses ptr=r0
romsub $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(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(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(uword x @R0, uword y @R1, uword width @R2, uword height @R3, ubyte fill @Pc) clobbers(A,X,Y)
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(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 $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
romsub $fef6 = FB_init() clobbers(A,X,Y)
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A ; also outputs width=r0, height=r1
romsub $fefc = FB_set_palette(ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y) ; also uses pointer=r0
romsub $feff = FB_cursor_position() clobbers(A,X,Y) ; uses x=r0, y=r1
romsub $ff02 = FB_cursor_next_line() clobbers(A,X,Y) ; uses x=r0
romsub $fef9 = FB_get_info() clobbers(X,Y) -> byte @A, uword @R0, uword @R1 ; width=r0, height=r1
romsub $fefc = FB_set_palette(uword pointer @R0, ubyte index @A, ubyte bytecount @X) clobbers(A,X,Y)
romsub $feff = FB_cursor_position(uword x @R0, uword y @R1) clobbers(A,X,Y)
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 $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 $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 $ff14 = FB_set_8_pixels_opaque(ubyte mask @A, ubyte color1 @X, ubyte color2 @Y) clobbers(A,X,Y) ; also uses r0L=pattern
romsub $ff17 = FB_fill_pixels(ubyte color @A) clobbers(A,X,Y) ; also uses count=r0, step=r1
romsub $ff1a = FB_filter_pixels() clobbers(A,X,Y) ; uses ptr=r0, count=r1
romsub $ff1d = FB_move_pixels() clobbers(A,X,Y) ; uses sx=r0, sy=r1, tx=r2, ty=r3, count=r4
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(uword count @R0, uword pstep @R1, ubyte color @A) clobbers(A,X,Y)
romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1) clobbers(A,X,Y)
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
romsub $fef0 = sprite_set_image(ubyte number @A, ubyte width @X, ubyte height @Y, ubyte apply_mask @Pc) clobbers(A,X,Y) -> ubyte @Pc ; also uses pixels=r0, mask=r1, bpp=r2L
romsub $fef3 = sprite_set_position(ubyte number @A) clobbers(A,X,Y) ; also uses x=r0 and y=r1
romsub $fee4 = memory_fill(ubyte value @A) clobbers(A,X,Y) ; uses address=r0, num_bytes=r1
romsub $fee7 = memory_copy() clobbers(A,X,Y) ; uses source=r0, target=r1, num_bytes=r2
romsub $feea = memory_crc() clobbers(A,X,Y) ; uses address=r0, num_bytes=r1 result->r2
romsub $feed = memory_decompress() clobbers(A,X,Y) ; uses input=r0, output=r1 result->r1
romsub $fedb = console_init() clobbers(A,X,Y) ; uses x=r0, y=r1, width=r2, height=r3
romsub $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(uword x @R0, uword y @R1, ubyte number @A) clobbers(A,X,Y)
romsub $fee4 = memory_fill(uword address @R0, uword num_bytes @R1, ubyte value @A) clobbers(A,X,Y)
romsub $fee7 = memory_copy(uword source @R0, uword target @R1, uword num_bytes @R2) clobbers(A,X,Y)
romsub $feea = memory_crc(uword address @R0, uword num_bytes @R1) clobbers(A,X,Y) -> uword @R2
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(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 $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
romsub $fed8 = console_put_image() clobbers(A,X,Y) ; uses ptr=r0, width=r1, height=r2
romsub $fed5 = console_set_paging_message() clobbers(A,X,Y) ; uses messageptr=r0
romsub $fed8 = console_put_image(uword pointer @R0, uword width @R1, uword height @R2) clobbers(A,X,Y)
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 $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
romsub $fecc = monitor() clobbers(A,X,Y)
; ---- end of kernal routines ----
; ---- 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 {{
stz cx16.VERA_CTRL
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_DATA0
lda cx16.VERA_DATA1
rts
}}
}
sub vpoke(ubyte bank, uword address, ubyte value) {
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
lda bank
and #1
sta cx16.VERA_ADDR_H
lda address
lda cx16.r0
sta cx16.VERA_ADDR_L
lda address+1
lda cx16.r0+1
sta cx16.VERA_ADDR_M
lda value
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
}}
@ -311,27 +452,7 @@ _loop ldy #0
}}
}
sub wait(uword jiffies) {
uword current_time = 0
c64.SETTIM(0,0,0)
while current_time < jiffies {
; read clock
%asm {{
phx
jsr c64.RDTIM
sta current_time
stx current_time+1
plx
}}
}
}
; ---- system stuff -----
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
@ -364,15 +485,128 @@ asmsub init_system() {
}}
}
asmsub reset_system() {
; Soft-reset the system back to Basic prompt.
%asm {{
sei
lda #14
sta $01
stz cx16.d1prb ; bank the kernal in
jmp (cx16.RESET_VEC)
}}
}
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,10 +15,23 @@ const ubyte DEFAULT_WIDTH = 80
const ubyte DEFAULT_HEIGHT = 60
sub clear_screen() {
sub clear_screen() {
txt.chrout(147)
}
sub home() {
txt.chrout(19)
}
sub nl() {
txt.chrout('\n')
}
sub spc() {
txt.chrout(' ')
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
%asm {{

View File

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

View File

@ -1,11 +1,13 @@
; C64 and Cx16 disk drive I/O routines.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
%import textio
%import string
%import syslib
; Note: this code is compatible with C64 and CX16.
diskio {
sub directory(ubyte drivenumber) -> ubyte {
; -- Prints the directory contents of disk drive 8-11 to the screen. Returns success.
@ -28,18 +30,19 @@ diskio {
ubyte low = c64.CHRIN()
ubyte high = c64.CHRIN()
txt.print_uw(mkword(high, low))
txt.chrout(' ')
txt.spc()
ubyte @zp char
do {
repeat {
char = c64.CHRIN()
if char==0
break
txt.chrout(char)
} until char==0
txt.chrout('\n')
}
txt.nl()
void c64.CHRIN() ; skip 2 bytes
void c64.CHRIN()
status = c64.READST()
void c64.STOP()
if_z
if c64.STOP2()
break
}
@ -48,10 +51,10 @@ io_error:
c64.CLRCHN() ; restore default i/o devices
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_ub(status)
txt.chrout('\n')
txt.nl()
return false
}
@ -60,32 +63,31 @@ io_error:
; internal variables for the iterative file lister / loader
ubyte list_suffixmatch
ubyte list_pattern_size
ubyte list_skip_disk_name
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 -----
; ----- get a list of files (uses iteration functions internally) -----
sub list_files(ubyte drivenumber, uword pattern, ubyte suffixmatch, uword name_ptrs, ubyte max_names) -> ubyte {
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.
ubyte[256] names_buffer
ubyte[256] names_buffer1 ; to store a bit more names
uword buf_ptr = &names_buffer
uword names_buffer = memory("filenames", 512)
uword buffer_start = names_buffer
ubyte files_found = 0
if lf_start_list(drivenumber, pattern, suffixmatch) {
if lf_start_list(drivenumber, pattern_ptr) {
while lf_next_entry() {
@(name_ptrs) = lsb(buf_ptr)
@(name_ptrs) = lsb(names_buffer)
name_ptrs++
@(name_ptrs) = msb(buf_ptr)
@(name_ptrs) = msb(names_buffer)
name_ptrs++
buf_ptr += strcopy(diskio.list_filename, buf_ptr) + 1
names_buffer += string.copy(diskio.list_filename, names_buffer) + 1
files_found++
if buf_ptr - &names_buffer > (len(names_buffer) + len(names_buffer1) - 18)
if names_buffer - buffer_start > 512-18
break
if files_found == max_names
break
@ -95,20 +97,15 @@ io_error:
return files_found
}
; ----- iterative file lister functions -----
; ----- iterative file lister functions (uses io channel 12) -----
sub lf_start_list(ubyte drivenumber, uword pattern, ubyte suffixmatch) -> ubyte {
; -- start an iterative file listing with optional prefix or suffix name matching.
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
list_suffixmatch = suffixmatch
list_pattern = pattern_ptr
list_skip_disk_name = true
iteration_in_progress = true
if pattern==0
list_pattern_size = 0
else
list_pattern_size = strlen(pattern)
c64.SETNAM(1, "$")
c64.SETLFS(12, drivenumber, 0)
@ -160,7 +157,7 @@ io_error:
; read the filename
repeat {
ubyte char = c64.CHRIN()
if_z
if char==0
break
if char=='\"'
break
@ -178,15 +175,9 @@ io_error:
void c64.CHRIN()
if not list_skip_disk_name {
if list_pattern_size {
; do filename matching
if list_suffixmatch
rightstr(list_filename, filename, list_pattern_size)
else
leftstr(list_filename, filename, list_pattern_size)
if strcmp(filename, list_pattern)==0
return true
} else
if not list_pattern
return true
if prog8_lib.pattern_match(list_filename, list_pattern)
return true
}
list_skip_disk_name = false
@ -207,46 +198,134 @@ close_end:
}
; ----- iterative file loader functions -----
; ----- 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(strlen(filenameptr), filenameptr)
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
return true
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 buffersize) -> uword {
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
uword actual = 0
void c64.CHKIN(11) ; use #11 as input channel again
repeat buffersize {
ubyte data = c64.CHRIN()
@(bufferpointer) = data
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++
actual++
ubyte status = c64.READST()
if status==64
f_close() ; end of file, close it
if status
return actual
list_blocks++
}
return actual
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 {
@ -283,7 +362,7 @@ io_error:
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)
uword end_address = address + size
@ -302,18 +381,18 @@ io_error:
plp
}}
ubyte result=0
first_byte = 0 ; result var reuse
if_cc
result = c64.READST()==0
first_byte = c64.READST()==0
c64.CLRCHN()
c64.CLOSE(1)
return result
return first_byte
}
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
c64.SETNAM(strlen(filenameptr), filenameptr)
c64.SETNAM(string.length(filenameptr), filenameptr)
ubyte secondary = 1
uword end_of_load = 0
if address_override
@ -345,10 +424,9 @@ io_error:
sub delete(ubyte drivenumber, uword filenameptr) {
; -- delete a file on the drive
ubyte flen = strlen(filenameptr)
filename[0] = 's'
filename[1] = ':'
memcopy(filenameptr, &filename+2, flen+1)
ubyte flen = string.copy(filenameptr, &filename+2)
c64.SETNAM(flen+2, filename)
c64.SETLFS(1, drivenumber, 15)
void c64.OPEN()
@ -358,13 +436,11 @@ io_error:
sub rename(ubyte drivenumber, uword oldfileptr, uword newfileptr) {
; -- rename a file on the drive
ubyte flen_old = strlen(oldfileptr)
ubyte flen_new = strlen(newfileptr)
filename[0] = 'r'
filename[1] = ':'
memcopy(newfileptr, &filename+2, flen_new)
ubyte flen_new = string.copy(newfileptr, &filename+2)
filename[flen_new+2] = '='
memcopy(oldfileptr, &filename+3+flen_new, flen_old+1)
ubyte flen_old = string.copy(oldfileptr, &filename+3+flen_new)
c64.SETNAM(3+flen_new+flen_old, filename)
c64.SETLFS(1, drivenumber, 15)
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.
;
; 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:
; http://6502org.wikidot.com/software-math
; http://codebase64.org/doku.php?id=base:6502_6510_maths
@ -1203,8 +1200,6 @@ mul_word_40 .proc
rol a
asl P8ZP_SCRATCH_W1
rol a
asl P8ZP_SCRATCH_W1
rol a
tay
lda P8ZP_SCRATCH_W1
rts

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
;
; indent format: TABS, size=8
math {
%asminclude "library:math.asm", ""

View File

@ -1078,292 +1078,3 @@ _loop_hi ldy _index_first
.pend
func_exit .proc
; -- immediately exit the program with a return code in the A register
ldx orig_stackpointer
txs
rts ; return to original caller
.pend
func_read_flags_stack .proc
; -- put the processor status register on the stack
php
pla
sta P8ESTACK_LO,x
dex
rts
.pend
func_memset .proc
; note: clobbers A,Y
txa
pha
lda _arg_address
sta P8ZP_SCRATCH_W1
lda _arg_address+1
sta P8ZP_SCRATCH_W1+1
ldx _arg_numbytes
ldy _arg_numbytes+1
lda _arg_bytevalue
jsr memset
pla
tax
rts
_arg_address .word 0
_arg_numbytes .word 0
_arg_bytevalue .byte 0
.pend
func_memsetw .proc
; note: clobbers A,Y
txa
pha
lda _arg_address
sta P8ZP_SCRATCH_W1
lda _arg_address+1
sta P8ZP_SCRATCH_W1+1
lda _arg_numwords
sta P8ZP_SCRATCH_W2
lda _arg_numwords+1
sta P8ZP_SCRATCH_W2+1
lda _arg_wordvalue
ldy _arg_wordvalue+1
jsr memsetw
pla
tax
rts
_arg_address .word 0
_arg_numwords .word 0
_arg_wordvalue .word 0
.pend
func_memcopy .proc
; memcopy of any number of bytes, note: clobbers A,Y
stx P8ZP_SCRATCH_REG
lda _arg_from
sta P8ZP_SCRATCH_W1
lda _arg_from+1
sta P8ZP_SCRATCH_W1+1
lda _arg_to
sta P8ZP_SCRATCH_W2
lda _arg_to+1
sta P8ZP_SCRATCH_W2+1
ldy #0
ldx _arg_numbytes+1
beq _remain
- lda (P8ZP_SCRATCH_W1),y ; move a page at a time
sta (P8ZP_SCRATCH_W2),y
iny
bne -
inc P8ZP_SCRATCH_W1+1
inc P8ZP_SCRATCH_W2+1
dex
bne -
_remain ldx _arg_numbytes
beq _done
- lda (P8ZP_SCRATCH_W1),y ; move the remaining bytes
sta (P8ZP_SCRATCH_W2),y
iny
dex
bne -
_done ldx P8ZP_SCRATCH_REG
rts
_arg_from .word 0
_arg_to .word 0
_arg_numbytes .word 0
.pend
func_memcopy255 .proc
; fast memcopy of up to 255 bytes, note: clobbers A,Y
; note: also uses the _arg variables from regular func_memcopy
stx P8ZP_SCRATCH_REG
lda func_memcopy._arg_from
sta P8ZP_SCRATCH_W1
lda func_memcopy._arg_from+1
sta P8ZP_SCRATCH_W1+1
lda func_memcopy._arg_to
sta P8ZP_SCRATCH_W2
lda func_memcopy._arg_to+1
sta P8ZP_SCRATCH_W2+1
ldx func_memcopy._arg_numbytes
ldy #0
- lda (P8ZP_SCRATCH_W1), y
sta (P8ZP_SCRATCH_W2), y
iny
dex
bne -
ldx P8ZP_SCRATCH_REG
rts
.pend
func_leftstr .proc
; leftstr(source, target, length)
lda _arg_source
ldy _arg_source+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy _arg_length
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
_arg_source .word 0
_arg_target .word 0
_arg_length .byte 0
.pend
func_rightstr .proc
; rightstr(source, target, length)
lda _arg_source
ldy _arg_source+1
jsr func_strlen_into_A
sec
sbc _arg_length
sta P8ZP_SCRATCH_B1
lda _arg_source
ldy _arg_source+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
sty P8ZP_SCRATCH_REG
- ldy P8ZP_SCRATCH_B1
lda (P8ZP_SCRATCH_W1),y
inc P8ZP_SCRATCH_B1
ldy P8ZP_SCRATCH_REG
sta (P8ZP_SCRATCH_W2),y
inc P8ZP_SCRATCH_REG
cmp #0
bne -
rts
_arg_source .word 0
_arg_target .word 0
_arg_length .byte 0
.pend
func_strlen_into_A .proc
; -- put length of 0-terminated string in A/Y into A
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
iny
bne -
+ tya
rts
.pend
func_strlen_stack .proc
jsr func_strlen_into_A
sta P8ESTACK_LO,x
dex
rts
.pend
func_substr .proc
; substr(source, target, start, length)
lda _arg_source
ldy _arg_source+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_target
ldy _arg_target+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy _arg_length
lda _arg_start
sta P8ZP_SCRATCH_B1
; 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
jmp _startloop
- lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
_startloop dey
cpy #$ff
bne -
rts
_arg_source .word 0
_arg_target .word 0
_arg_start .byte 0
_arg_length .byte 0
.pend
func_strcmp .proc
; -- compare 2 strings -> A
lda _arg_s2
ldy _arg_s2+1
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
lda _arg_s1
ldy _arg_s1+1
jmp strcmp_mem
_arg_s1 .word 0
_arg_s2 .word 0
.pend
func_strcmp_stack .proc
jsr func_strcmp
sta P8ESTACK_LO,x
dex
rts
.pend
func_strcopy .proc
lda _arg_to
ldy _arg_to+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda _arg_from
ldy _arg_from+1
jsr strcpy
tya
rts
_arg_from .word 0
_arg_to .word 0
.pend
func_strcopy_to_stack .proc
jsr func_strcopy
sta P8ESTACK_LO,x
dex
rts
.pend

View File

@ -1,9 +1,7 @@
; Prog8 internal library routines - always included by the compiler
; Internal library routines - always included by the compiler
; Generic machine independent 6502 code.
;
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
;
; indent format: TABS, size=8
read_byte_from_address_on_stack .proc
@ -1021,33 +1019,33 @@ _arg_s2 .word 0
strcmp_mem .proc
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
lda (P8ZP_SCRATCH_W1),y
bne +
lda (P8ZP_SCRATCH_W2),y
bne _return_minusone
beq _return
+ lda (P8ZP_SCRATCH_W2),y
sec
sbc (P8ZP_SCRATCH_W1),y
bmi _return_one
bne _return_minusone
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
_loop ldy #0
lda (P8ZP_SCRATCH_W1),y
bne +
lda (P8ZP_SCRATCH_W2),y
bne _return_minusone
beq _return
+ lda (P8ZP_SCRATCH_W2),y
sec
sbc (P8ZP_SCRATCH_W1),y
bmi _return_one
bne _return_minusone
inc P8ZP_SCRATCH_W1
bne +
inc P8ZP_SCRATCH_W1+1
+ inc P8ZP_SCRATCH_W2
bne _loop
inc P8ZP_SCRATCH_W2+1
bne _loop
_return_one
lda #1
_return rts
lda #1
_return rts
_return_minusone
lda #-1
rts
.pend
lda #-1
rts
.pend
sign_extend_stack_byte .proc

View File

@ -1,10 +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
;
; indent format: TABS, size=8
prog8_lib {
%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

@ -1,48 +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
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 @@
5.4
6.0

View File

@ -32,20 +32,20 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
private fun compileMain(args: Array<String>) {
val cli = CommandLineInterface("prog8compiler")
val startEmulator by cli.flagArgument("-emu", "auto-start emulator after successful compilation")
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.flagArgument("-slowwarn", "show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.flagValueArgument("-target", "compilertarget",
"target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available", C64Target.name)
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
val cli = ArgParser("prog8compiler")
val startEmulator by cli.option(ArgType.Boolean, shortName="emu", description = "auto-start emulator after successful compilation")
val outputDir by cli.option(ArgType.String, shortName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.option(ArgType.Boolean, shortName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, shortName = "noopt", description = "don't perform any optimizations")
val watchMode by cli.option(ArgType.Boolean, shortName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, shortName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.option(ArgType.String, shortName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try {
cli.parse(args)
} catch (e: Exception) {
} catch (e: IllegalStateException) {
System.err.println(e.message)
exitProcess(1)
}
@ -55,19 +55,22 @@ private fun compileMain(args: Array<String>) {
exitProcess(1)
}
if(watchMode) {
if(watchMode==true) {
val watchservice = FileSystems.getDefault().newWatchService()
val allImportedFiles = mutableSetOf<Path>()
while(true) {
println("Continuous watch mode active. Modules: $moduleFiles")
val results = mutableListOf<CompilationResult>()
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
val compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
results.add(compilationResult)
}
val allImportedFiles = results.flatMap { it.importedFiles }
val allNewlyImportedFiles = results.flatMap { it.importedFiles }
allImportedFiles.addAll(allNewlyImportedFiles)
println("Imported files (now watching:)")
for (importedFile in allImportedFiles) {
print(" ")
@ -98,7 +101,7 @@ private fun compileMain(args: Array<String>) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, slowCodegenWarnings, compilationTarget, outputPath)
compilationResult = compileProgram(filepath, dontOptimize!=true, dontWriteAssembly!=true, slowCodegenWarnings==true, compilationTarget, outputPath)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {
@ -107,10 +110,10 @@ private fun compileMain(args: Array<String>) {
exitProcess(1)
}
if (startEmulator) {
if (startEmulator==true) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else if(startEmulator) {
else {
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
}
}

View File

@ -172,8 +172,19 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(") ")
}
if(subroutine.returntypes.any()) {
val rt = subroutine.returntypes.single()
output("-> ${datatypeString(rt)} ")
if(subroutine.asmReturnvaluesRegisters.isNotEmpty()) {
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)
outputln("= ${subroutine.asmAddress.toHex()}")

View File

@ -175,6 +175,7 @@ interface INameScope {
}
}
fun containsDefinedVariables() = statements.any { it is VarDecl && (it !is ParameterVarDecl) }
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoCodeNorVars() = !containsCodeOrVars()

View File

@ -225,11 +225,12 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
}
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
val inline = this.inline()!=null
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, null, true, statements, toPosition())
subdecl.asmClobbers, null, true, inline, statements, toPosition())
}
private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
@ -237,7 +238,8 @@ private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
val address = integerliteral().toAst().number.toInt()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, mutableListOf(), toPosition())
subdecl.asmClobbers, address, true, inline = false, statements = mutableListOf(), position = toPosition()
)
}
private class AsmsubDecl(val name: String,
@ -272,13 +274,13 @@ private class AsmSubroutineReturn(val type: DataType,
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
= asmsub_return().map {
val register = it.identifier()?.toAst()
val register = it.register().text
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (val name = register.nameInSource.single()) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
when (register) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
else -> throw FatalAstException("invalid register or status flag in $it")
}
}
@ -293,14 +295,14 @@ private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParamete
= asmsub_param().map {
val vardecl = it.vardecl()
val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
val register = it.identifier()?.toAst()
val register = it.register().text
var registerorpair: RegisterOrPair? = null
var statusregister: Statusflag? = null
if(register!=null) {
when (val name = register.nameInSource.single()) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(name)
in Statusflag.names -> statusregister = Statusflag.valueOf(name)
else -> throw FatalAstException("invalid register or status flag '$name'")
when (register) {
in RegisterOrPair.names -> registerorpair = RegisterOrPair.valueOf(register)
in Statusflag.names -> statusregister = Statusflag.valueOf(register)
else -> throw FatalAstException("invalid register or status flag '$register'")
}
}
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
@ -341,11 +343,13 @@ private fun prog8Parser.LabeldefContext.toAst(): Statement =
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
// non-asm subroutine
val inline = inline()!=null
val returntypes = sub_return_part()?.toAst() ?: emptyList()
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
returntypes,
statement_block()?.toAst() ?: mutableListOf(),
inline,
toPosition())
}
@ -371,7 +375,7 @@ private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
}
private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
val names = this.identifier().map { it.toAst().nameInSource.single() }
val names = this.cpuregister().map { it.text }
return names.map { CpuRegister.valueOf(it) }.toSet()
}
@ -623,7 +627,7 @@ private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
if(stmt!=null)
stmtBlock.add(stmt)
val scope = AnonymousScope(stmtBlock, toPosition())
return WhenChoice(values, scope, toPosition())
return WhenChoice(values?.toMutableList(), scope, toPosition())
}
private fun prog8Parser.VardeclContext.toAst(): VarDecl {

View File

@ -82,7 +82,10 @@ enum class RegisterOrPair {
AY,
XY,
FAC1,
FAC2;
FAC2,
// cx16 virtual registers:
R0, R1, R2, R3, R4, R5, R6, R7,
R8, R9, R10, R11, R12, R13, R14, R15;
companion object {
val names by lazy { values().map { it.toString()} }
@ -92,9 +95,9 @@ enum class RegisterOrPair {
enum class Statusflag {
Pc,
Pz,
Pz, // don't use
Pv,
Pn;
Pn; // don't use
companion object {
val names by lazy { values().map { it.toString()} }
@ -127,6 +130,7 @@ val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
val StringlyDatatypes = setOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
val IterableDatatypes = setOf(
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B,
@ -148,6 +152,11 @@ val ElementArrayTypes = mapOf(
DataType.UWORD to DataType.ARRAY_UW,
DataType.FLOAT to DataType.ARRAY_F
)
val Cx16VirtualRegisters = listOf(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)
// find the parent node of a specific type or interface
// (useful to figure out in what namespace/block something is defined, etc)

View File

@ -11,6 +11,7 @@ import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.functions.BuiltinFunctions
import prog8.functions.builtinFunctionReturnType
import java.io.File
internal class AstChecker(private val program: Program,
@ -34,24 +35,6 @@ internal class AstChecker(private val program: Program,
if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
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,
@ -185,6 +168,23 @@ internal class AstChecker(private val program: Program,
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("statement occurs in a block, where it will never be executed. Use it in a subroutine instead.", statement.position)
break
}
}
super.visit(block)
}
@ -202,10 +202,20 @@ internal class AstChecker(private val program: Program,
if(subroutine.name in BuiltinFunctions)
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()
if(uniqueNames.size!=subroutine.parameters.size)
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)
// user-defined subroutines can only have zero or one return type
@ -219,8 +229,8 @@ internal class AstChecker(private val program: Program,
if (subroutine.amountOfRtsInAsm() == 0) {
if (subroutine.returntypes.isNotEmpty()) {
// for asm subroutines with an address, no statement check is possible.
if (subroutine.asmAddress == null)
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)")
if (subroutine.asmAddress == null && !subroutine.inline)
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)")
}
}
}
@ -289,6 +299,22 @@ internal class AstChecker(private val program: Program,
regCounts[CpuRegister.Y]=regCounts.getValue(CpuRegister.Y)+1
}
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> { /* no sensible way to count this */ }
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 -> { /* no sensible way to count this */ }
null ->
if(p.statusflag!=null)
statusflagCounts[p.statusflag] = statusflagCounts.getValue(p.statusflag) + 1
@ -341,6 +367,13 @@ internal class AstChecker(private val program: Program,
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) {
if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
@ -582,7 +615,7 @@ internal class AstChecker(private val program: Program,
err("memory address must be valid integer 0..\$ffff", decl.value?.position)
}
} else {
err("value of memory mapped variable can only be a number, perhaps you meant to use an address pointer type instead?", decl.value?.position)
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)
}
}
}
@ -846,6 +879,10 @@ internal class AstChecker(private val program: Program,
override fun visit(typecast: TypecastExpression) {
if(typecast.type in IterableDatatypes)
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)
}
@ -931,11 +968,19 @@ internal class AstChecker(private val program: Program,
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
if(targetStatement!=null)
checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
if(!functionCallStatement.void && targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
if(targetStatement.returntypes.size==1)
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)
if (!functionCallStatement.void) {
// check for unused return values
if (targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
if(targetStatement.returntypes.size==1)
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") {
@ -988,8 +1033,6 @@ internal class AstChecker(private val program: Program,
}
}
} 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) {
for (arg in args.zip(target.parameters)) {
val argIDt = arg.first.inferType(program)

View File

@ -31,6 +31,13 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
else
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)
}
@ -112,9 +119,12 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
val labelOrVar = subroutine.getLabelOrVariable(name)
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
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)
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}) {

View File

@ -69,10 +69,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
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program.namespace)
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) {
is NumericLiteralValue -> {
arrayIndexedExpression.indexer.indexNum = expr2
@ -148,28 +174,22 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
val indexerVarPrefix = "prog8_autovar_index_"
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
// TODO make this even smarter so that an indexerVar can be reused for a different following statement... requires updating the partOfStatement?
var indexerVar = repo.firstOrNull { it.replaces isSameAs expr.indexer }
if(indexerVar==null) {
// add another loop index var to be used for this expression
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer, statement)
repo.add(indexerVar)
// create the indexer var at block level scope
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
}
indexerVar.used++ // keep track of how many times it it used, to avoid assigning it multiple times
// TODO make this a bit smarter so it can reuse indexer variables. BUT BEWARE of scoping+initialization problems then
// add another loop index var to be used for this expression
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
val indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer)
repo.add(indexerVar)
// create the indexer var at block level scope
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
// replace the indexer with just the variable
// assign the indexing expression to the helper variable, but only if that hasn't been done already
val indexerExpression = expr.indexer.origExpression!!
val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
if(indexerVar.used==1) {
val assign = Assignment(target, indexerExpression, indexerExpression.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
}
val assign = Assignment(target, indexerExpression, indexerExpression.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
modifications.add(IAstModification.SetExpression( {
expr.indexer.indexVar = it as IdentifierReference
expr.indexer.indexNum = null

View File

@ -4,9 +4,9 @@ import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
@ -61,8 +61,15 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
// multiple return values will NOT work inside an expression.
// they MIGHT work in a regular assignment or just a function call statement.
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
if(call !is FunctionCallStatement && parent !is Assignment && parent !is VarDecl) {
return "can't use subroutine call that returns multiple return values here (try moving it into a separate assignment)"
if (call !is FunctionCallStatement) {
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"
}
}
}
}

View File

@ -698,7 +698,7 @@ class AsmGenInfo {
var usedFloatEvalResultVar1 = false
var usedFloatEvalResultVar2 = false
class ArrayIndexerInfo(val name: String, val replaces: ArrayIndex, val partOfStatement: Statement, var used: Int=0)
class ArrayIndexerInfo(val name: String, val replaces: ArrayIndex)
}
// the subroutine class covers both the normal user-defined subroutines,
@ -712,11 +712,12 @@ class Subroutine(override val name: String,
val asmClobbers: Set<CpuRegister>,
val asmAddress: Int?,
val isAsmSubroutine: Boolean,
val inline: Boolean,
override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope {
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, position: Position)
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, statements, position)
constructor(name: String, parameters: List<SubroutineParameter>, returntypes: List<DataType>, statements: MutableList<Statement>, inline: Boolean, position: Position)
: this(name, parameters, returntypes, emptyList(), determineReturnRegisters(returntypes), emptySet(), null, false, inline, statements, position)
companion object {
private fun determineReturnRegisters(returntypes: List<DataType>): List<RegisterOrStatusflag> {
@ -724,7 +725,7 @@ class Subroutine(override val name: String,
return when(returntypes.singleOrNull()) {
in ByteDatatypes -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null))
in WordDatatypes -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null))
DataType.FLOAT -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null))
DataType.FLOAT -> listOf(RegisterOrStatusflag(RegisterOrPair.FAC1, null))
null -> emptyList()
else -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null))
}
@ -757,8 +758,17 @@ class Subroutine(override val name: String,
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun regXasParam() = asmParameterRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun shouldPreserveStatusRegisterAfterCall() = asmReturnvaluesRegisters.any { it.statusflag != null } || shouldSaveX()
fun shouldSaveX() = CpuRegister.X in asmClobbers || regXasResult() || regXasParam()
fun shouldKeepA(): Pair<Boolean, Boolean> {
// determine if A's value should be kept when preparing for calling the subroutine, and when returning from it
if(!isAsmSubroutine)
return Pair(false, false)
// it seems that we never have to save A when calling? will be loaded correctly after setup.
// but on return it depends on wether the routine returns something in A.
val saveAonReturn = asmReturnvaluesRegisters.any { it.registerOrPair==RegisterOrPair.A || it.registerOrPair==RegisterOrPair.AY || it.registerOrPair==RegisterOrPair.AX }
return Pair(false, saveAonReturn)
}
fun amountOfRtsInAsm(): Int = statements
.asSequence()
@ -981,7 +991,7 @@ class WhenStatement(var condition: Expression,
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class WhenChoice(var values: List<Expression>?, // if null, this is the 'else' part
class WhenChoice(var values: MutableList<Expression>?, // if null, this is the 'else' part
var statements: AnonymousScope,
override val position: Position) : Node {
override lateinit var parent: Node
@ -998,7 +1008,9 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
statements = replacement
replacement.parent = this
} else if(choiceValues!=null && node in choiceValues) {
throw FatalAstException("cannot replace choice values")
val idx = choiceValues.indexOf(node)
choiceValues[idx] = replacement as Expression
replacement.parent = this
} else {
throw FatalAstException("invalid replacement")
}

View File

@ -111,6 +111,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null
&& !subroutine.inline
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return

View File

@ -30,6 +30,7 @@ data class CompilationOptions(val output: OutputType,
val floats: Boolean,
val noSysInit: Boolean) {
var slowCodegenWarnings = false
var optimize = false
}

View File

@ -51,17 +51,18 @@ fun compileProgram(filepath: Path,
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (optimize)
if (compilationOptions.optimize)
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
}
System.out.flush()
System.err.flush()
@ -218,7 +219,7 @@ private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerO
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
optimize: Boolean, compilerOptions: CompilationOptions): String {
compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
programAst.processAstBeforeAsmGeneration(errors)
errors.handle()
@ -231,7 +232,7 @@ private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir:
errors,
CompilationTarget.instance.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly(optimize)
outputDir).compileToAssembly()
assembly.assemble(compilerOptions)
errors.handle()
return assembly.name

View File

@ -3,7 +3,7 @@ package prog8.compiler.target
import prog8.compiler.CompilationOptions
internal interface IAssemblyGenerator {
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
fun compileToAssembly(): IAssemblyProgram
}
internal const val generatedLabelPrefix = "_prog8_label_"

View File

@ -32,7 +32,6 @@ internal interface IMachineDefinition {
fun initializeZeropage(compilerOptions: CompilationOptions)
fun getFloat(num: Number): IMachineFloat
fun getFloatRomConst(number: Double): String?
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
fun launchEmulator(programName: String)
fun isRegularRAMaddress(address: Int): Boolean

View File

@ -7,7 +7,6 @@ import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat
import prog8.parser.ModuleImporter
import java.io.IOException
import java.math.RoundingMode
import kotlin.math.absoluteValue
import kotlin.math.pow
@ -24,7 +23,6 @@ internal object C64MachineDefinition: IMachineDefinition {
override val RAW_LOAD_ADDRESS = 0xc000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above
override val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
override val ESTACK_HI = 0xcf00 // $ce00-$ceff inclusive
@ -32,41 +30,6 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun getFloatRomConst(number: Double): String? {
// try to match the ROM float constants to save memory
val mflpt5 = Mflpt5.fromNumber(number)
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
when {
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ZERO_const" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_ONE_const" // not a ROM const
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "floats.FL_PIVAL"
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_N32768"
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FONE"
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRHLF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "floats.FL_SQRTWO"
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "floats.FL_NEGHLF"
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "floats.FL_LOG2"
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "floats.FL_TENC"
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "floats.FL_NZMIL"
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FHALF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "floats.FL_LOGEB2"
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_PIHALF"
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "floats.FL_TWOPI"
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "floats.FL_FR4"
else -> {
// attempt to correct for a few rounding issues
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
3.1415926536 -> return "floats.FL_PIVAL"
1.4142135624 -> return "floats.FL_SQRTWO"
0.7071067812 -> return "floats.FL_SQRHLF"
0.6931471806 -> return "floats.FL_LOG2"
else -> {}
}
}
}
return null
}
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib")

View File

@ -1,5 +1,6 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
@ -50,8 +51,10 @@ internal class AsmGen(private val program: Program,
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
internal val loopEndLabels = ArrayDeque<String>()
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
internal val slabs = mutableMapOf<String, Int>()
internal val removals = mutableListOf<Pair<Statement, INameScope>>()
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
override fun compileToAssembly(): IAssemblyProgram {
assemblyLines.clear()
loopEndLabels.clear()
@ -63,6 +66,13 @@ internal class AsmGen(private val program: Program,
throw AssemblyError("first block should be 'main'")
for(b in program.allBlocks())
block2asm(b)
for(removal in removals.toList()) {
removal.second.remove(removal.first)
removals.remove(removal)
}
slaballocations()
footer()
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
@ -70,7 +80,7 @@ internal class AsmGen(private val program: Program,
for (line in assemblyLines) { it.println(line) }
}
if(optimize) {
if(options.optimize) {
assemblyLines.clear()
assemblyLines.addAll(outputFile.readLines())
var optimizationsDone = 1
@ -143,15 +153,23 @@ internal class AsmGen(private val program: Program,
if(options.zeropage !in setOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
out("""
; zeropage is clobbered so we need to reset the machine at exit
lda #>${CompilationTarget.instance.name}.reset_system
lda #>sys.reset_system
pha
lda #<${CompilationTarget.instance.name}.reset_system
lda #<sys.reset_system
pha""")
}
out(" jmp main.start ; start program / force start proc to be included")
}
private fun slaballocations() {
out("; memory slabs")
out("prog8_slabs\t.block")
for((name, size) in slabs)
out("$name\t.fill $size")
out("\t.bend")
}
private fun footer() {
// the global list of all floating point constants for the whole program
out("; global float constants")
@ -470,16 +488,13 @@ internal class AsmGen(private val program: Program,
}
internal fun getFloatAsmConst(number: Double): String {
var asmName = CompilationTarget.instance.machine.getFloatRomConst(number)
if(asmName.isNullOrEmpty()) {
// no ROM float const for this value, create our own
asmName = globalFloatConsts[number]
if(asmName==null) {
asmName = "prog8_float_const_${globalFloatConsts.size}"
globalFloatConsts[number] = asmName
}
}
return asmName
val asmName = globalFloatConsts[number]
if(asmName!=null)
return asmName
val newName = "prog8_float_const_${globalFloatConsts.size}"
globalFloatConsts[number] = newName
return newName
}
internal fun asmSymbolName(identifier: IdentifierReference): String {
@ -511,51 +526,53 @@ internal class AsmGen(private val program: Program,
val sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program.namespace)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
return if(scopedName in allocatedZeropageVariables) {
// pointervar is already in the zero page, no need to copy
out(" ldy #0 | lda ($sourceName),y")
Pair(true, sourceName)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" lda ($sourceName)")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda (P8ZP_SCRATCH_W1)""")
Pair(false, sourceName)
}
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y""")
Pair(false, sourceName)
}
}
fun storeByteIntoPointer(pointervar: IdentifierReference, ldaInstructionArg: String?) {
val sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program.namespace)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if(scopedName in allocatedZeropageVariables) {
// pointervar is already in the zero page, no need to copy
if(ldaInstructionArg!=null)
out(" lda $ldaInstructionArg")
out(" ldy #0 | sta ($sourceName),y")
} else {
out("""
ldy $sourceName
sty P8ZP_SCRATCH_W2
ldy $sourceName+1
sty P8ZP_SCRATCH_W2+1
${if(ldaInstructionArg==null) "" else "lda $ldaInstructionArg"}
ldy #0
sta (P8ZP_SCRATCH_W2),y""")
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" ldy #0 | lda ($sourceName),y")
Pair(true, sourceName)
} else {
out("""
lda $sourceName
ldy $sourceName+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y""")
Pair(false, sourceName)
}
}
}
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
internal fun saveRegister(register: CpuRegister, dontUseStack: Boolean, scope: Subroutine) {
if(dontUseStack) {
internal fun saveRegisterLocal(register: CpuRegister, scope: Subroutine) {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
// just use the cpu's stack for all registers, shorter code
when (register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> out(" phx")
CpuRegister.Y -> out(" phy")
}
} else {
when (register) {
CpuRegister.A -> {
out(" sta _prog8_regsaveA")
scope.asmGenInfo.usedRegsaveA = true
// just use the stack, only for A
out(" pha")
}
CpuRegister.X -> {
out(" stx _prog8_regsaveX")
@ -566,52 +583,79 @@ internal class AsmGen(private val program: Program,
scope.asmGenInfo.usedRegsaveY = true
}
}
}
}
} else {
when (register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
else {
out(" stx _prog8_regsaveX")
scope.asmGenInfo.usedRegsaveX = true
}
internal fun saveRegisterStack(register: CpuRegister, keepA: Boolean) {
when (register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | txa | pha | lda P8ZP_SCRATCH_REG")
else
out(" txa | pha")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
else {
out(" sty _prog8_regsaveY")
scope.asmGenInfo.usedRegsaveY = true
}
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | tya | pha | lda P8ZP_SCRATCH_REG")
else
out(" tya | pha")
}
}
}
}
internal fun restoreRegister(register: CpuRegister, dontUseStack: Boolean) {
if(dontUseStack) {
internal fun restoreRegisterLocal(register: CpuRegister) {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
when (register) {
CpuRegister.A -> out(" sta _prog8_regsaveA")
// this just used the stack, for all registers. Shorter code.
CpuRegister.A -> out(" pla")
CpuRegister.X -> out(" plx")
CpuRegister.Y -> out(" ply")
}
} else {
when (register) {
CpuRegister.A -> out(" pla") // this just used the stack but only for A
CpuRegister.X -> out(" ldx _prog8_regsaveX")
CpuRegister.Y -> out(" ldy _prog8_regsaveY")
}
}
}
} else {
when (register) {
CpuRegister.A -> out(" pla")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
else out(" ldx _prog8_regsaveX")
internal fun restoreRegisterStack(register: CpuRegister, keepA: Boolean) {
when (register) {
CpuRegister.A -> {
if(keepA)
throw AssemblyError("can't set keepA if A is restored")
out(" pla")
}
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
else
out(" pla | tax")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
else out(" ldy _prog8_regsaveY")
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
else
out(" pla | tay")
}
}
}
}
internal fun translate(stmt: Statement) {
outputSourceLine(stmt)
when(stmt) {
@ -741,8 +785,14 @@ internal class AsmGen(private val program: Program,
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack)
internal fun translateFunctionCall(functionCall: FunctionCall, preserveStatusRegisterAfterCall: Boolean) =
functioncallAsmGen.translateFunctionCall(functionCall, preserveStatusRegisterAfterCall)
internal fun translateFunctionCall(functionCall: FunctionCall) =
functioncallAsmGen.translateFunctionCall(functionCall)
internal fun saveXbeforeCall(functionCall: IFunctionCall) =
functioncallAsmGen.saveXbeforeCall(functionCall)
internal fun restoreXafterCall(functionCall: IFunctionCall) =
functioncallAsmGen.restoreXafterCall(functionCall)
internal fun translateNormalAssignment(assign: AsmAssignment) =
assignmentAsmGen.translateNormalAssignment(assign)
@ -758,9 +808,19 @@ internal class AsmGen(private val program: Program,
private fun translateSubroutine(sub: Subroutine) {
if(sub.inline) {
if(options.optimize)
return // inline subroutines don't exist anymore on their own
else if(sub.amountOfRtsInAsm()==0) {
// make sure the NOT INLINED subroutine actually does an rts at the end
sub.statements.add(Return(null, Position.DUMMY))
}
}
out("")
outputSourceLine(sub)
if(sub.isAsmSubroutine) {
if(sub.asmAddress!=null)
return // already done at the memvars section
@ -785,7 +845,7 @@ internal class AsmGen(private val program: Program,
}
out("""
tsx
stx prog8_lib.orig_stackpointer ; required for func_exit
stx prog8_lib.orig_stackpointer ; required for sys.exit()
ldx #255 ; init estack ptr
clv
clc""")
@ -793,6 +853,14 @@ internal class AsmGen(private val program: Program,
out("; statements")
sub.statements.forEach{ translate(it) }
for(removal in removals.toList()) {
if(removal.second==sub) {
removal.second.remove(removal.first)
removals.remove(removal)
}
}
out("; variables")
out("; register saves")
if(sub.asmGenInfo.usedRegsaveA)
@ -879,7 +947,7 @@ internal class AsmGen(private val program: Program,
}
is NumericLiteralValue -> {
val iterations = (stmt.iterations as NumericLiteralValue).number.toInt()
if(iterations<0 || iterations > 65536)
if(iterations<0 || iterations > 65535)
throw AssemblyError("invalid number of iterations")
when {
iterations == 0 -> {}
@ -1145,10 +1213,10 @@ $counterVar .byte 0""")
"%asminclude" -> {
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
val scopeprefix = stmt.args[1].str ?: ""
if(!scopeprefix.isBlank())
if(scopeprefix.isNotBlank())
out("$scopeprefix\t.proc")
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
if(!scopeprefix.isBlank())
if(scopeprefix.isNotBlank())
out(" .pend\n")
}
"%asmbinary" -> {
@ -1195,12 +1263,8 @@ $label nop""")
throw AssemblyError("normal subroutines can't return value in status register directly")
when (returnType) {
in IntegerDatatypes -> {
assignmentAsmGen.assignExpressionToRegister(returnvalue, returnReg.registerOrPair)
}
DataType.FLOAT -> {
// return the float value via FAC1
assignExpressionToRegister(returnvalue, RegisterOrPair.FAC1)
in NumericDatatypes -> {
assignExpressionToRegister(returnvalue, returnReg.registerOrPair)
}
else -> {
// all else take its address and assign that also to AY register pair
@ -1260,4 +1324,92 @@ $label nop""")
else -> throw AssemblyError("need byte type")
}
}
internal fun isZpVar(scopedName: String): Boolean = scopedName in allocatedZeropageVariables
internal fun isZpVar(variable: IdentifierReference): Boolean {
val vardecl = variable.targetVarDecl(program.namespace)!!
return vardecl.makeScopedName(vardecl.name) in allocatedZeropageVariables
}
internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: Expression): Pair<Expression, Expression>? {
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {
val leftDt = pointerOffsetExpr.left.inferType(program)
val rightDt = pointerOffsetExpr.left.inferType(program)
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UBYTE))
return Pair(pointerOffsetExpr.left, pointerOffsetExpr.right)
if(leftDt.istype(DataType.UBYTE) && rightDt.istype(DataType.UWORD))
return Pair(pointerOffsetExpr.right, pointerOffsetExpr.left)
if(leftDt.istype(DataType.UWORD) && rightDt.istype(DataType.UWORD)) {
// could be that the index was a constant numeric byte but converted to word, check that
val constIdx = pointerOffsetExpr.right.constValue(program)
if(constIdx!=null && constIdx.number.toInt()>=0 && constIdx.number.toInt()<=255) {
return Pair(pointerOffsetExpr.left, NumericLiteralValue(DataType.UBYTE, constIdx.number, constIdx.position))
}
// could be that the index was type casted into uword, check that
val rightTc = pointerOffsetExpr.right as? TypecastExpression
if(rightTc!=null && rightTc.expression.inferType(program).istype(DataType.UBYTE))
return Pair(pointerOffsetExpr.left, rightTc.expression)
val leftTc = pointerOffsetExpr.left as? TypecastExpression
if(leftTc!=null && leftTc.expression.inferType(program).istype(DataType.UBYTE))
return Pair(pointerOffsetExpr.right, leftTc.expression)
}
}
return null
}
internal fun tryOptimizedPointerAccessWithA(expr: BinaryExpression, write: Boolean): Boolean {
// optimize pointer,indexregister if possible
if(expr.operator=="+") {
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
if(ptrAndIndex!=null) {
val pointervar = ptrAndIndex.first as? IdentifierReference
if(write) {
when(ptrAndIndex.second) {
is NumericLiteralValue, is IdentifierReference -> {
if(pointervar!=null && isZpVar(pointervar)) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" sta (P8ZP_SCRATCH_W2),y")
}
}
else -> {
// same as above but we need to save the A register
if(pointervar!=null && isZpVar(pointervar)) {
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" pla")
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
}
}
}
} else {
if(pointervar!=null && isZpVar(pointervar)) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" lda (P8ZP_SCRATCH_W2),y")
}
}
return true
}
}
return false
}
}

View File

@ -179,7 +179,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
}
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN 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>()
for (pair in linesByFour) {
@ -196,10 +196,14 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
(first.startsWith("sty ") && second.startsWith("ldy ")) ||
(first.startsWith("stx ") && second.startsWith("ldx "))
) {
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
val third = pair[2].value.trimStart()
if(!third.startsWith("b")) {
// no branch instruction follows, we can potentiall remove the load instruction
val firstLoc = first.substring(4).trimStart()
val secondLoc = second.substring(4).trimStart()
if (firstLoc == secondLoc) {
mods.add(Modification(pair[1].index, true, null))
}
}
}
}

View File

@ -7,8 +7,6 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
@ -35,144 +33,66 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(discardResult && resultToStack)
throw AssemblyError("cannot both discard the result AND put it onto stack")
val scope = (fcall as Node).definingSubroutine()!!
val sscope = (fcall as Node).definingSubroutine()
when (func.name) {
"msb" -> funcMsb(fcall, resultToStack)
"lsb" -> funcLsb(fcall, resultToStack)
"mkword" -> funcMkword(fcall, resultToStack)
"abs" -> funcAbs(fcall, func, resultToStack, scope)
"abs" -> funcAbs(fcall, func, resultToStack, sscope)
"swap" -> funcSwap(fcall)
"min", "max" -> funcMinMax(fcall, func, resultToStack)
"sum" -> funcSum(fcall, resultToStack)
"any", "all" -> funcAnyAll(fcall, func, resultToStack)
"sin8", "sin8u", "sin16", "sin16u",
"cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, scope)
"sgn" -> funcSgn(fcall, func, resultToStack, scope)
"cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, sscope)
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, scope)
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, sscope)
"rnd", "rndw" -> funcRnd(func, resultToStack)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, scope)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, sscope)
"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")
}
"read_flags" -> {
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_read_flags_stack")
else
asmgen.out(" php | pla")
}
"clear_carry" -> asmgen.out(" clc")
"set_carry" -> asmgen.out(" sec")
"clear_irqd" -> asmgen.out(" cli")
"set_irqd" -> asmgen.out(" sei")
"strlen" -> funcStrlen(fcall, resultToStack)
"strcmp" -> funcStrcmp(fcall, func, resultToStack, scope)
"strcopy" -> {
translateArguments(fcall.args, func, scope)
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_strcopy_to_stack")
else
asmgen.out(" jsr prog8_lib.func_strcopy")
}
"memcopy", "memset", "memsetw" -> funcMemSetCopy(fcall, func, scope)
"substr", "leftstr", "rightstr" -> {
translateArguments(fcall.args, func, scope)
asmgen.out(" jsr prog8_lib.func_${func.name}")
}
"exit" -> {
translateArguments(fcall.args, func, scope)
asmgen.out(" jmp prog8_lib.func_exit")
}
"progend" -> {
if(resultToStack)
asmgen.out("""
lda #<prog8_program_end
sta P8ESTACK_LO,x
lda #>prog8_program_end
sta P8ESTACK_HI,x
dex""")
else
asmgen.out(" lda #<prog8_program_end | ldy #>prog8_program_end")
}
"memory" -> funcMemory(fcall, discardResult, resultToStack)
else -> TODO("missing asmgen for builtin func ${func.name}")
}
}
private fun funcMemSetCopy(fcall: IFunctionCall, func: FSignature, scope: Subroutine) {
if(CompilationTarget.instance is Cx16Target) {
when(func.name) {
"memset" -> {
// use the ROM function of the Cx16
asmgen.assignExpressionToVariable(fcall.args[0], "cx16.r0", DataType.UWORD, scope)
asmgen.assignExpressionToVariable(fcall.args[1], "cx16.r1", DataType.UWORD, scope)
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.A)
val sub = (fcall as FunctionCallStatement).definingSubroutine()!!
asmgen.saveRegister(CpuRegister.X, false, sub)
asmgen.out(" jsr cx16.memory_fill")
asmgen.restoreRegister(CpuRegister.X, false)
}
"memcopy" -> {
val count = fcall.args[2].constValue(program)?.number?.toInt()
val countDt = fcall.args[2].inferType(program)
if((count!=null && count <= 255) || countDt.istype(DataType.UBYTE) || countDt.istype(DataType.BYTE)) {
// fast memcopy of up to 255
translateArguments(fcall.args, func, scope)
asmgen.out(" jsr prog8_lib.func_memcopy255")
return
}
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean) {
if(discardResult || fcall !is FunctionCall)
throw AssemblyError("should not discard result of memory allocation at $fcall")
val scope = fcall.definingScope()
val nameRef = fcall.args[0] as IdentifierReference
val name = (nameRef.targetVarDecl(program.namespace)!!.value as StringLiteralValue).value
val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
// use the ROM function of the Cx16
asmgen.assignExpressionToVariable(fcall.args[0], "cx16.r0", DataType.UWORD, scope)
asmgen.assignExpressionToVariable(fcall.args[1], "cx16.r1", DataType.UWORD, scope)
asmgen.assignExpressionToVariable(fcall.args[2], "cx16.r2", DataType.UWORD, scope)
val sub = (fcall as FunctionCallStatement).definingSubroutine()!!
asmgen.saveRegister(CpuRegister.X, false, sub)
asmgen.out(" jsr cx16.memory_copy")
asmgen.restoreRegister(CpuRegister.X, false)
}
"memsetw" -> {
translateArguments(fcall.args, func, scope)
asmgen.out(" jsr prog8_lib.func_memsetw")
}
}
} else {
if(func.name=="memcopy") {
val count = fcall.args[2].constValue(program)?.number?.toInt()
val countDt = fcall.args[2].inferType(program)
if((count!=null && count <= 255) || countDt.istype(DataType.UBYTE) || countDt.istype(DataType.BYTE)) {
translateArguments(fcall.args, func, scope)
asmgen.out(" jsr prog8_lib.func_memcopy255")
return
}
}
translateArguments(fcall.args, func, scope)
asmgen.out(" jsr prog8_lib.func_${func.name}")
}
val existingSize = asmgen.slabs[name]
if(existingSize!=null && existingSize!=size)
throw AssemblyError("memory slab '$name' already exists with a different size ($size) at ${fcall.position}")
val slabname = IdentifierReference(listOf("prog8_slabs", name), fcall.position)
slabname.linkParents(fcall)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
val target =
if(resultToStack)
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
else
AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen)
val assign = AsmAssignment(src, target, false, fcall.position)
asmgen.translateNormalAssignment(assign)
// remove the variable for the name, it's not used as a variable only as a tag for the assembler.
val nameDecl = scope.statements.single { it is VarDecl && it.name==nameRef.nameInSource.single() }
asmgen.removals.add(Pair(nameDecl, scope))
asmgen.slabs[name] = size
}
private fun funcStrcmp(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine) {
translateArguments(fcall.args, func, scope)
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_strcmp_stack")
else
asmgen.out(" jsr prog8_lib.func_strcmp")
}
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine) {
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
@ -180,7 +100,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
}
private fun funcSinCosInt(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine) {
private fun funcSinCosInt(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
@ -320,11 +240,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" ror ${number.toHex()}")
} else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
asmgen.out("""
sta (+) + 1
sty (+) + 2
+ ror ${'$'}ffff ; modified""")
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
if(ptrAndIndex!=null) {
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
sta (+) + 1
sty (+) + 2
+ ror ${'$'}ffff,x ; modified""")
} else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
asmgen.out("""
sta (+) + 1
sty (+) + 2
+ ror ${'$'}ffff ; modified""")
}
}
}
is IdentifierReference -> {
@ -409,11 +341,23 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" rol ${number.toHex()}")
} else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
asmgen.out("""
sta (+) + 1
sty (+) + 2
+ rol ${'$'}ffff ; modified""")
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
if(ptrAndIndex!=null) {
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as FunctionCallStatement).definingSubroutine()!!)
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
asmgen.restoreRegisterLocal(CpuRegister.X)
asmgen.out("""
sta (+) + 1
sty (+) + 2
+ rol ${'$'}ffff,x ; modified""")
} else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
asmgen.out("""
sta (+) + 1
sty (+) + 2
+ rol ${'$'}ffff ; modified""")
}
}
}
is IdentifierReference -> {
@ -446,7 +390,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.assignExpressionToVariable(indexerExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
}
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine) {
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
if(resultToStack)
asmgen.out(" jsr floats.func_${func.name}_stack")
@ -454,7 +398,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr floats.func_${func.name}_fac1")
}
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine) {
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
@ -546,27 +490,6 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun funcStrlen(fcall: IFunctionCall, resultToStack: Boolean) {
if (fcall.args[0] is IdentifierReference) {
// use the address of the variable
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")
type.istype(DataType.UWORD) -> asmgen.out(" lda $name | ldy $name+1")
else -> throw AssemblyError("strlen requires str or uword arg")
}
}
else {
// use the expression value as address of the string
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
}
if (resultToStack)
asmgen.out(" jsr prog8_lib.func_strlen_stack")
else
asmgen.out(" jsr prog8_lib.func_strlen_into_A")
}
private fun funcSwap(fcall: IFunctionCall) {
val first = fcall.args[0]
val second = fcall.args[1]
@ -611,6 +534,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// optimized simple case: swap two memory locations
if(first is DirectMemoryRead && second is DirectMemoryRead) {
// TODO optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+1), @(ptr+2))
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
@ -944,7 +868,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine) {
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program).typeOrElse(DataType.STRUCT)
if(resultToStack) {
@ -1023,6 +947,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" sta P8ESTACK_LO,x | dex")
} else {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
if (resultToStack)
asmgen.out(" sta P8ESTACK_LO,x | dex")
}
@ -1042,7 +969,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
""")
}
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine) {
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
val callConv = signature.callConvention(args.map { it.inferType(program).typeOrElse(DataType.STRUCT) })
fun getSourceForFloat(value: Expression): AsmAssignSource {
@ -1055,6 +982,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("float literals should have been converted into autovar")
}
else -> {
if(scope==null)
throw AssemblyError("cannot use float arguments outside of a subroutine scope")
scope.asmGenInfo.usedFloatEvalResultVar2 = true
val variable = IdentifierReference(listOf(subroutineFloatEvalResultVar2), value.position)
val addr = AddressOf(variable, value.position)

View File

@ -74,6 +74,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is FunctionCall -> {
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall)
asmgen.out(" cmp #0")
asmgen.out(" bne $jumpIfFalseLabel")
return
}
@ -112,6 +114,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is FunctionCall -> {
if(dt in ByteDatatypes) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.A)
if(left is FunctionCall)
asmgen.out(" cmp #0")
asmgen.out(" beq $jumpIfFalseLabel")
return
}
@ -1320,52 +1324,60 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
+""")
}
private fun translateFunctionCallResultOntoStack(expression: FunctionCall) {
private fun translateFunctionCallResultOntoStack(call: FunctionCall) {
// only for use in nested expression evaluation
val sub = expression.target.targetStatement(program.namespace)
val sub = call.target.targetStatement(program.namespace)
if(sub is BuiltinFunctionStatementPlaceholder) {
val builtinFunc = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(expression, builtinFunc, true)
asmgen.translateBuiltinFunctionCallExpression(call, builtinFunc, true)
} else {
sub as Subroutine
val preserveStatusRegisterAfterCall = sub.shouldPreserveStatusRegisterAfterCall()
asmgen.translateFunctionCall(expression, preserveStatusRegisterAfterCall)
asmgen.saveXbeforeCall(call)
asmgen.translateFunctionCall(call)
if(sub.regXasResult()) {
// store the return value in X somewhere that we can acces again below
asmgen.out(" stx P8ZP_SCRATCH_REG")
}
asmgen.restoreXafterCall(call)
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
// result value in cpu or status registers, put it on the stack
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
if (reg.registerOrPair != null) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" sta P8ESTACK_LO,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
RegisterOrPair.X -> {
// return value in X register has been discarded, just push a zero
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
asmgen.out(" stz P8ESTACK_LO,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_LO,x")
asmgen.out(" dex")
}
RegisterOrPair.AX -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" sta P8ESTACK_LO,x")
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
asmgen.out(" stz P8ESTACK_HI,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x")
asmgen.out(" dex")
}
RegisterOrPair.XY -> {
// return value in X register has been discarded, just push a zero in this place
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
asmgen.out(" stz P8ESTACK_LO,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_LO,x")
asmgen.out(" tya | sta P8ESTACK_HI,x | dex")
}
RegisterOrPair.X -> asmgen.out(" lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.AX -> asmgen.out(" sta P8ESTACK_LO,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_HI,x | dex")
RegisterOrPair.XY -> asmgen.out(" tya | sta P8ESTACK_HI,x | lda P8ZP_SCRATCH_REG | sta P8ESTACK_LO,x | dex")
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.push_fac1")
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.push_fac2")
RegisterOrPair.R0,
RegisterOrPair.R1,
RegisterOrPair.R2,
RegisterOrPair.R3,
RegisterOrPair.R4,
RegisterOrPair.R5,
RegisterOrPair.R6,
RegisterOrPair.R7,
RegisterOrPair.R8,
RegisterOrPair.R9,
RegisterOrPair.R10,
RegisterOrPair.R11,
RegisterOrPair.R12,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> {
asmgen.out("""
lda cx16.${reg.registerOrPair.toString().toLowerCase()}
sta P8ESTACK_LO,x
lda cx16.${reg.registerOrPair.toString().toLowerCase()}+1
sta P8ESTACK_HI,x
dex
""")
}
}
}
else if(reg.statusflag!=null) {
@ -1445,6 +1457,24 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
}
internal fun translateDirectMemReadExpression(expr: DirectMemoryRead, pushResultOnEstack: Boolean) {
fun assignViaExprEval() {
asmgen.assignExpressionToVariable(expr.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
if (pushResultOnEstack) {
asmgen.out(" lda (P8ZP_SCRATCH_W2) | dex | sta P8ESTACK_LO+1,x")
} else {
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
}
} else {
if (pushResultOnEstack) {
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y | dex | sta P8ESTACK_LO+1,x")
} else {
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
}
}
}
when(expr.addressExpression) {
is NumericLiteralValue -> {
val address = (expr.addressExpression as NumericLiteralValue).number.toInt()
@ -1458,14 +1488,15 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(pushResultOnEstack)
asmgen.out(" sta P8ESTACK_LO,x | dex")
}
else -> {
asmgen.assignExpressionToVariable(expr.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if(pushResultOnEstack) {
asmgen.out(" dex | ldy #0 | lda (P8ZP_SCRATCH_W2),y | sta P8ESTACK_LO+1,x")
is BinaryExpression -> {
if(asmgen.tryOptimizedPointerAccessWithA(expr.addressExpression as BinaryExpression, false)) {
if(pushResultOnEstack)
asmgen.out(" sta P8ESTACK_LO,x | dex")
} else {
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
assignViaExprEval()
}
}
else -> assignViaExprEval()
}
}

View File

@ -5,33 +5,54 @@ 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.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.assignment.*
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCallStatement(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program.namespace)!!
val preserveStatusRegisterAfterCall = sub.shouldPreserveStatusRegisterAfterCall()
translateFunctionCall(stmt, preserveStatusRegisterAfterCall)
// functioncalls no longer return results on the stack, so simply ignore the results in the registers
if(preserveStatusRegisterAfterCall)
asmgen.out(" plp") // restore status flags from call
saveXbeforeCall(stmt)
translateFunctionCall(stmt)
restoreXafterCall(stmt)
// just ignore any result values from the function call.
}
internal fun translateFunctionCall(stmt: IFunctionCall, preserveStatusRegisterAfterCall: Boolean) {
// output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values!
internal fun saveXbeforeCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val saveX = sub.shouldSaveX()
if(saveX)
asmgen.saveRegister(CpuRegister.X, preserveStatusRegisterAfterCall, (stmt as Node).definingSubroutine()!!)
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.namespace) ?: 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.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
if(sub.asmParameterRegisters.isEmpty()) {
@ -45,37 +66,43 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
} else {
// multiple register arguments, risk of register clobbering.
// evaluate arguments onto the stack, and load the registers from the evaluated values on the stack.
when {
stmt.args.all {it is AddressOf ||
it is NumericLiteralValue ||
it is StringLiteralValue ||
it is ArrayLiteralValue ||
it is IdentifierReference} -> {
// no risk of clobbering for these simple argument types. Optimize the register loading.
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
argumentViaRegister(sub, arg.first, arg.second)
}
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
val (vregsArgsInfo, otherRegsArgsInfo) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
for(arg in vregsArgsInfo)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in otherRegsArgsInfo)
argumentViaRegister(sub, arg.first.first, arg.first.second)
}
else -> {
// Risk of clobbering due to complex expression args. Work via the stack.
// Risk of clobbering due to complex expression args. Evaluate first, then assign registers.
registerArgsViaStackEvaluation(stmt, sub)
}
}
}
}
}
asmgen.out(" jsr $subName")
if(preserveStatusRegisterAfterCall) {
asmgen.out(" php") // 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(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")
}
if(saveX)
asmgen.restoreRegister(CpuRegister.X, preserveStatusRegisterAfterCall)
// 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) {
@ -96,6 +123,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
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)
@ -111,15 +139,40 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
argForAregister = argi
}
argi.value.second.registerOrPair == RegisterOrPair.Y -> {
asmgen.out(" ldy P8ESTACK_LO+${argi.index},x")
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 (CompilationTarget.instance.machine.cpu == 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+${argForCarry.index},x
lda P8ESTACK_LO$plusIdxStr,x
beq +
sec
bcs ++
@ -128,21 +181,23 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
if(argForAregister!=null) {
val plusIdxStr = if(argForAregister.index==0) "" else "+${argForAregister.index}"
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")
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+${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")
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)
@ -233,7 +288,11 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.assignVariableToRegister(scratchVar, register)
}
else {
val target = AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
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)

View File

@ -91,7 +91,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
else
{
asmgen.loadScaledArrayIndexIntoRegister(targetArrayIdx, elementDt, CpuRegister.A)
asmgen.saveRegister(CpuRegister.X, false, scope!!)
asmgen.saveRegisterLocal(CpuRegister.X, scope!!)
asmgen.out(" tax")
when(elementDt) {
in ByteDatatypes -> {
@ -105,7 +105,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
lda $asmArrayvarname,x
bne +
dec $asmArrayvarname+1,x
+ dec $asmArrayvarname
+ dec $asmArrayvarname,x
""")
}
DataType.FLOAT -> {
@ -119,7 +119,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
else -> throw AssemblyError("weird array elt dt")
}
asmgen.restoreRegister(CpuRegister.X, false)
asmgen.restoreRegisterLocal(CpuRegister.X)
}
}
}

View File

@ -77,6 +77,22 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
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)
}
}
}
@ -134,7 +150,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
is FunctionCall -> {
when (val sub = value.target.targetStatement(program.namespace)) {
is Subroutine -> {
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null }?.first
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
?: throw AssemblyError("can't translate zero return values in assignment")
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)

View File

@ -112,6 +112,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
SourceStorageKind.MEMORY -> {
fun assignViaExprEval(expression: Expression) {
assignExpressionToVariable(expression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
assignRegisterByte(assign.target, CpuRegister.A)
}
val value = assign.source.memory!!
when (value.addressExpression) {
is NumericLiteralValue -> {
@ -121,11 +130,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is IdentifierReference -> {
assignMemoryByte(assign.target, null, value.addressExpression as IdentifierReference)
}
else -> {
assignExpressionToVariable(value.addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope)
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
assignRegisterByte(assign.target, CpuRegister.A)
is BinaryExpression -> {
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
assignRegisterByte(assign.target, CpuRegister.A)
} else {
assignViaExprEval(value.addressExpression)
}
}
else -> assignViaExprEval(value.addressExpression)
}
}
SourceStorageKind.EXPRESSION -> {
@ -142,11 +154,13 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is FunctionCall -> {
when (val sub = value.target.targetStatement(program.namespace)) {
is Subroutine -> {
val preserveStatusRegisterAfterCall = sub.shouldPreserveStatusRegisterAfterCall()
asmgen.translateFunctionCall(value, preserveStatusRegisterAfterCall)
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.registerOrPair!=null }
asmgen.saveXbeforeCall(value)
asmgen.translateFunctionCall(value)
val returnValue = sub.returntypes.zip(sub.asmReturnvaluesRegisters).singleOrNull { it.second.registerOrPair!=null } ?:
sub.returntypes.zip(sub.asmReturnvaluesRegisters).single { it.second.statusflag!=null }
when (returnValue.first) {
DataType.STR -> {
asmgen.restoreXafterCall(value)
when(assign.target.datatype) {
DataType.UWORD -> {
// assign the address of the string result value
@ -168,9 +182,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
DataType.FLOAT -> {
// float result from function sits in FAC1
asmgen.restoreXafterCall(value)
assignFAC1float(assign.target)
}
else -> {
// do NOT restore X register before assigning the result values first
when (returnValue.second.registerOrPair) {
RegisterOrPair.A -> assignRegisterByte(assign.target, CpuRegister.A)
RegisterOrPair.X -> assignRegisterByte(assign.target, CpuRegister.X)
@ -178,12 +194,18 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> assignRegisterpairWord(assign.target, RegisterOrPair.AX)
RegisterOrPair.AY -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
RegisterOrPair.XY -> assignRegisterpairWord(assign.target, RegisterOrPair.XY)
else -> throw AssemblyError("should be just one register byte result value")
else -> {
val sflag = returnValue.second.statusflag
if(sflag!=null)
assignStatusFlagByte(assign.target, sflag)
else
throw AssemblyError("should be just one register byte result value")
}
}
// we've processed the result value in the X register by now, so it's now finally safe to restore it
asmgen.restoreXafterCall(value)
}
}
if (preserveStatusRegisterAfterCall)
asmgen.out(" plp") // restore status flags from call
}
is BuiltinFunctionStatementPlaceholder -> {
val signature = BuiltinFunctions.getValue(sub.name)
@ -194,7 +216,22 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(returntype.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
DataType.STR -> throw AssemblyError("missing code for assign string from builtin func => copy string or assign string address")
DataType.STR -> {
when (assign.target.datatype) {
DataType.STR -> {
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
DataType.UWORD -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
else -> throw AssemblyError("str return value type mismatch with target")
}
}
DataType.FLOAT -> {
// float result from function sits in FAC1
assignFAC1float(assign.target)
@ -211,12 +248,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// Everything else just evaluate via the stack.
// (we can't use the assignment helper functions to do it via registers here,
// because the code here is the implementation of exactly that...)
if(value.parent is Return) {
if (value.parent is Return) {
if (this.asmgen.options.slowCodegenWarnings)
println("warning: slow stack evaluation used for return: $value target=${assign.target.kind} at ${value.position}")
}
asmgen.translateExpression(value)
if(assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
exprAsmgen.translateExpression(value)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
assignStackValue(assign.target)
}
@ -231,6 +268,24 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun assignStatusFlagByte(target: AsmAssignTarget, statusflag: Statusflag) {
when(statusflag) {
Statusflag.Pc -> {
asmgen.out(" lda #0 | rol a")
}
Statusflag.Pv -> {
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+""")
}
else -> throw AssemblyError("can't use Z or N flags as return 'values'")
}
assignRegisterByte(target, CpuRegister.A)
}
private fun assignTypeCastedValue(target: AsmAssignTarget, targetDt: DataType, value: Expression, origTypeCastExpression: TypecastExpression) {
val valueIDt = value.inferType(program)
if(!valueIDt.isKnown)
@ -254,14 +309,37 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
is DirectMemoryRead -> {
if(targetDt in WordDatatypes) {
if (value.addressExpression is NumericLiteralValue) {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
assignMemoryByteIntoWord(target, address, null)
return
fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
assignRegisterByte(target, CpuRegister.A)
}
else if (value.addressExpression is IdentifierReference) {
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
return
when (value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
assignMemoryByteIntoWord(target, address, null)
return
}
is IdentifierReference -> {
assignMemoryByteIntoWord(target, null, value.addressExpression as IdentifierReference)
return
}
is BinaryExpression -> {
if(asmgen.tryOptimizedPointerAccessWithA(value.addressExpression as BinaryExpression, false)) {
asmgen.out(" ldy #0")
assignRegisterpairWord(target, RegisterOrPair.AY)
} else {
assignViaExprEval(value.addressExpression)
}
}
else -> {
assignViaExprEval(value.addressExpression)
}
}
}
}
@ -297,6 +375,52 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
return
}
if(origTypeCastExpression.type == DataType.UBYTE) {
val parentTc = origTypeCastExpression.parent as? TypecastExpression
if(parentTc!=null && parentTc.type==DataType.UWORD) {
// typecast something to ubyte and directly back to uword
// generate code for lsb(value) here instead of the ubyte typecast
return assignCastViaLsbFunc(value, target)
}
}
if(valueDt==DataType.UBYTE) {
when(target.register) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> {
// 'cast' a ubyte value to a byte register; no cast needed at all
return assignExpressionToRegister(value, target.register)
}
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
// cast an ubyte value to a 16 bits register, just assign it and make use of the value extension
return assignExpressionToRegister(value, target.register!!)
}
else -> {}
}
} else if(valueDt==DataType.UWORD) {
when(target.register) {
RegisterOrPair.A,
RegisterOrPair.X,
RegisterOrPair.Y -> {
// cast an uword to a byte register, do this via lsb(value)
// generate code for lsb(value) here instead of the ubyte typecast
return assignCastViaLsbFunc(value, target)
}
RegisterOrPair.AX,
RegisterOrPair.AY,
RegisterOrPair.XY,
in Cx16VirtualRegisters -> {
// 'cast' uword into a 16 bits register, just assign it
return assignExpressionToRegister(value, target.register!!)
}
else -> {}
}
}
// give up, do it via eval stack
// TODO optimize typecasts for more special cases?
// note: cannot use assignTypeCastedValue because that is ourselves :P
@ -306,6 +430,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
assignStackValue(target)
}
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
lsb.linkParents(value.parent)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
val assign = AsmAssignment(src, target, false, value.position)
translateNormalAssignment(assign)
}
private fun assignTypecastedFloatFAC1(targetAsmVarName: String, targetDt: DataType) {
if(targetDt==DataType.FLOAT)
@ -618,8 +750,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
TargetStorageKind.MEMORY -> {
asmgen.out(" inx")
storeByteViaRegisterAInMemoryAddress("P8ESTACK_LO,x", target.memory!!)
asmgen.out(" inx | lda P8ESTACK_LO,x")
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if(target.constArrayIndexValue!=null) {
@ -686,16 +818,34 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.A -> asmgen.out(" inx | lda P8ESTACK_LO,x")
RegisterOrPair.X -> throw AssemblyError("can't load X from stack here - use intermediary var? ${target.origAstTarget?.position}")
RegisterOrPair.Y -> asmgen.out(" inx | ldy P8ESTACK_LO,x")
RegisterOrPair.AX -> asmgen.out(" inx | lda P8ESTACK_LO,x | ldx #0")
RegisterOrPair.AY -> asmgen.out(" inx | lda P8ESTACK_LO,x | ldy #0")
RegisterOrPair.AX -> asmgen.out(" inx | txy | ldx #0 | lda P8ESTACK_LO,y")
RegisterOrPair.AY -> asmgen.out(" inx | ldy #0 | lda P8ESTACK_LO,x")
in Cx16VirtualRegisters -> {
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta cx16.${target.register.toString().toLowerCase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("can't assign byte from stack to register pair XY")
}
}
DataType.UWORD, DataType.WORD, in PassByReferenceDatatypes -> {
when(target.register!!) {
RegisterOrPair.AX -> throw AssemblyError("can't load X from stack here - use intermediary var? ${target.origAstTarget?.position}")
RegisterOrPair.AY-> asmgen.out(" inx | lda P8ESTACK_LO,x | ldy P8ESTACK_HI,x")
RegisterOrPair.AY-> asmgen.out(" inx | ldy P8ESTACK_HI,x | lda P8ESTACK_LO,x")
RegisterOrPair.XY-> throw AssemblyError("can't load X from stack here - use intermediary var? ${target.origAstTarget?.position}")
in Cx16VirtualRegisters -> {
asmgen.out("""
inx
lda P8ESTACK_LO,x
sta cx16.${target.register.toString().toLowerCase()}
lda P8ESTACK_HI,x
sta cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("can't assign word to single byte register")
}
}
@ -733,9 +883,17 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
RegisterOrPair.AX -> asmgen.out(" ldx #>$sourceName | lda #<$sourceName")
RegisterOrPair.AY -> asmgen.out(" ldy #>$sourceName | lda #<$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy #>$sourceName | ldx #<$sourceName")
in Cx16VirtualRegisters -> {
asmgen.out("""
lda #<$sourceName
sta cx16.${target.register.toString().toLowerCase()}
lda #>$sourceName
sta cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("can't load address in a single 8-bit register")
}
}
@ -866,9 +1024,17 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
RegisterOrPair.AX -> asmgen.out(" ldx $sourceName+1 | lda $sourceName")
RegisterOrPair.AY -> asmgen.out(" ldy $sourceName+1 | lda $sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy $sourceName+1 | ldx $sourceName")
in Cx16VirtualRegisters -> {
asmgen.out("""
lda $sourceName
sta cx16.${target.register.toString().toLowerCase()}
lda $sourceName+1
sta cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("can't load word in a single 8-bit register")
}
}
@ -1013,7 +1179,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.MEMORY -> {
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!)
asmgen.out(" lda $sourceName")
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
@ -1030,10 +1197,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.A -> asmgen.out(" lda $sourceName")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName")
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda $sourceName")
RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda $sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out("""
lda $sourceName
sta cx16.${target.register.toString().toLowerCase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("weird register")
}
}
TargetStorageKind.STACK -> {
@ -1128,18 +1304,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
else {
asmgen.loadScaledArrayIndexIntoRegister(wordtarget.array!!, wordtarget.datatype, CpuRegister.Y)
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname},y | iny")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" stz ${wordtarget.asmVarname},y")
else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname},y")
asmgen.out("""
lda $sourceName
sta ${wordtarget.asmVarname},y
iny
lda #0
sta ${wordtarget.asmVarname},y""")
}
}
TargetStorageKind.REGISTER -> {
when(wordtarget.register!!) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda $sourceName")
RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda $sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName")
else -> throw AssemblyError("only reg pairs are words")
}
}
@ -1155,13 +1332,20 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
require(target.datatype in ByteDatatypes)
if(target.register !in Cx16VirtualRegisters)
require(target.datatype in ByteDatatypes)
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" st${register.name.toLowerCase()} ${target.asmVarname}")
}
TargetStorageKind.MEMORY -> {
storeRegisterInMemoryAddress(register, target.memory!!)
when(register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
@ -1190,6 +1374,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> { asmgen.out(" ldx #0") }
RegisterOrPair.XY -> { asmgen.out(" tax | ldy #0") }
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" sta cx16.${target.register.toString().toLowerCase()}")
}
else -> throw AssemblyError("weird register")
}
CpuRegister.X -> when(target.register!!) {
RegisterOrPair.A -> { asmgen.out(" txa") }
@ -1199,6 +1388,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> { asmgen.out(" txa | ldx #0") }
RegisterOrPair.XY -> { asmgen.out(" ldy #0") }
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" stx cx16.${target.register.toString().toLowerCase()}")
}
else -> throw AssemblyError("weird register")
}
CpuRegister.Y -> when(target.register!!) {
RegisterOrPair.A -> { asmgen.out(" tya") }
@ -1208,6 +1402,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> { asmgen.out(" tya | ldx #0") }
RegisterOrPair.XY -> { asmgen.out(" tya | tax | ldy #0") }
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected type cast to float")
in Cx16VirtualRegisters -> {
// only assign a single byte to the virtual register's Lsb
asmgen.out(" sty cx16.${target.register.toString().toLowerCase()}")
}
else -> throw AssemblyError("weird register")
}
}
}
@ -1267,18 +1466,36 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG") }
RegisterOrPair.AX -> { }
RegisterOrPair.XY -> { asmgen.out(" stx P8ZP_SCRATCH_REG | ldy P8ZP_SCRATCH_REG | tax") }
in Cx16VirtualRegisters -> {
asmgen.out("""
sta cx16.${target.register.toString().toLowerCase()}
stx cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("expected reg pair")
}
RegisterOrPair.AY -> when(target.register!!) {
RegisterOrPair.AY -> { }
RegisterOrPair.AX -> { asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
RegisterOrPair.XY -> { asmgen.out(" tax") }
in Cx16VirtualRegisters -> {
asmgen.out("""
sta cx16.${target.register.toString().toLowerCase()}
sty cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("expected reg pair")
}
RegisterOrPair.XY -> when(target.register!!) {
RegisterOrPair.AY -> { asmgen.out(" txa") }
RegisterOrPair.AX -> { asmgen.out(" txa | sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG") }
RegisterOrPair.XY -> { }
in Cx16VirtualRegisters -> {
asmgen.out("""
stx cx16.${target.register.toString().toLowerCase()}
sty cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("expected reg pair")
}
else -> throw AssemblyError("expected reg pair")
@ -1296,6 +1513,43 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
if(word==0 && CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
// optimize setting zero value for this processor
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" stz ${target.asmVarname} | stz ${target.asmVarname}+1")
}
TargetStorageKind.MEMORY -> {
throw AssemblyError("no asm gen for assign word $word to memory ${target.memory}")
}
TargetStorageKind.ARRAY -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y)
asmgen.out("""
lda #0
sta ${target.asmVarname},y
sta ${target.asmVarname}+1,y
""")
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" lda #0 | tax")
RegisterOrPair.AY -> asmgen.out(" lda #0 | tay")
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
in Cx16VirtualRegisters -> {
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()} | stz cx16.${target.register.toString().toLowerCase()}+1")
}
else -> throw AssemblyError("invalid register for word value")
}
}
TargetStorageKind.STACK -> {
asmgen.out(" stz P8ESTACK_LO,x | stz P8ESTACK_HI,x | dex")
}
}
return
}
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if (word ushr 8 == word and 255) {
@ -1328,10 +1582,18 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.REGISTER -> {
when(target.register!!) {
RegisterOrPair.AX -> asmgen.out(" lda #<${word.toHex()} | ldx #>${word.toHex()}")
RegisterOrPair.AY -> asmgen.out(" lda #<${word.toHex()} | ldy #>${word.toHex()}")
RegisterOrPair.XY -> asmgen.out(" ldx #<${word.toHex()} | ldy #>${word.toHex()}")
else -> throw AssemblyError("can't assign word to single byte register")
RegisterOrPair.AX -> asmgen.out(" ldx #>${word.toHex()} | lda #<${word.toHex()}")
RegisterOrPair.AY -> asmgen.out(" ldy #>${word.toHex()} | lda #<${word.toHex()}")
RegisterOrPair.XY -> asmgen.out(" ldy #>${word.toHex()} | ldx #<${word.toHex()}")
in Cx16VirtualRegisters -> {
asmgen.out("""
lda #<${word.toHex()}
sta cx16.${target.register.toString().toLowerCase()}
lda #>${word.toHex()}
sta cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("invalid register for word value")
}
}
TargetStorageKind.STACK -> {
@ -1346,12 +1608,55 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
if(byte==0.toShort() && CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
// optimize setting zero value for this cpu
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" stz ${target.asmVarname} ")
}
TargetStorageKind.MEMORY -> {
asmgen.out(" lda #${byte.toHex()}")
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
val indexValue = target.constArrayIndexValue!!
asmgen.out(" stz ${target.asmVarname}+$indexValue")
}
else {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UBYTE, CpuRegister.Y)
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
}
}
TargetStorageKind.REGISTER -> when(target.register!!) {
RegisterOrPair.A -> asmgen.out(" lda #0")
RegisterOrPair.X -> asmgen.out(" ldx #0")
RegisterOrPair.Y -> asmgen.out(" ldy #0")
RegisterOrPair.AX -> asmgen.out(" lda #0 | tax")
RegisterOrPair.AY -> asmgen.out(" lda #0 | tay")
RegisterOrPair.XY -> asmgen.out(" ldx #0 | ldy #0")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()} | stz cx16.${target.register.toString().toLowerCase()}+1")
}
else -> throw AssemblyError("weird register")
}
TargetStorageKind.STACK -> {
asmgen.out(" stz P8ESTACK_LO,x | dex")
}
}
return
}
when(target.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" lda #${byte.toHex()} | sta ${target.asmVarname} ")
}
TargetStorageKind.MEMORY -> {
storeByteViaRegisterAInMemoryAddress("#${byte.toHex()}", target.memory!!)
asmgen.out(" lda #${byte.toHex()}")
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
@ -1367,10 +1672,18 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.A -> asmgen.out(" lda #${byte.toHex()}")
RegisterOrPair.X -> asmgen.out(" ldx #${byte.toHex()}")
RegisterOrPair.Y -> asmgen.out(" ldy #${byte.toHex()}")
RegisterOrPair.AX -> asmgen.out(" lda #${byte.toHex()} | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda #${byte.toHex()} | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx #${byte.toHex()} | ldy #0")
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda #${byte.toHex()}")
RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda #${byte.toHex()}")
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx #${byte.toHex()}")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out(" lda #${byte.toHex()} | sta cx16.${target.register.toString().toLowerCase()}")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()}+1\n")
else
asmgen.out(" lda #0 | sta cx16.${target.register.toString().toLowerCase()}+1\n")
}
else -> throw AssemblyError("weird register")
}
TargetStorageKind.STACK -> {
asmgen.out("""
@ -1527,7 +1840,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.MEMORY -> {
storeByteViaRegisterAInMemoryAddress(address.toHex(), target.memory!!)
asmgen.out(" lda ${address.toHex()}")
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
throw AssemblyError("no asm gen for assign memory byte at $address to array ${target.asmVarname}")
@ -1536,10 +1850,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.A -> asmgen.out(" lda ${address.toHex()}")
RegisterOrPair.X -> asmgen.out(" ldx ${address.toHex()}")
RegisterOrPair.Y -> asmgen.out(" ldy ${address.toHex()}")
RegisterOrPair.AX -> asmgen.out(" lda ${address.toHex()} | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda ${address.toHex()} | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldy ${address.toHex()} | ldy #0")
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda ${address.toHex()}")
RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda ${address.toHex()}")
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldy ${address.toHex()}")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out("""
lda ${address.toHex()}
sta cx16.${target.register.toString().toLowerCase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("weird register")
}
TargetStorageKind.STACK -> {
asmgen.out("""
@ -1555,8 +1878,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" sta ${target.asmVarname}")
}
TargetStorageKind.MEMORY -> {
val sourceName = asmgen.asmVariableName(identifier)
storeByteViaRegisterAInMemoryAddress(sourceName, target.memory!!)
asmgen.loadByteFromPointerIntoA(identifier)
storeRegisterAInMemoryAddress(target.memory!!)
}
TargetStorageKind.ARRAY -> {
throw AssemblyError("no asm gen for assign memory byte $identifier to array ${target.asmVarname} ")
@ -1571,6 +1894,14 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AY -> asmgen.out(" ldy #0")
RegisterOrPair.XY -> asmgen.out(" tax | ldy #0")
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out("""
sta cx16.${target.register.toString().toLowerCase()}
lda #0
sta cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("weird register")
}
}
TargetStorageKind.STACK -> {
@ -1595,9 +1926,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
throw AssemblyError("no asm gen for assign memory byte at $address to array ${wordtarget.asmVarname}")
}
TargetStorageKind.REGISTER -> when(wordtarget.register!!) {
RegisterOrPair.AX -> asmgen.out(" lda ${address.toHex()} | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda ${address.toHex()} | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldy ${address.toHex()} | ldy #0")
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda ${address.toHex()}")
RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda ${address.toHex()}")
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldy ${address.toHex()}")
else -> throw AssemblyError("word regs can only be pair")
}
TargetStorageKind.STACK -> {
@ -1644,46 +1975,76 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
private fun storeByteViaRegisterAInMemoryAddress(ldaInstructionArg: String, memoryAddress: DirectMemoryWrite) {
private fun storeRegisterAInMemoryAddress(memoryAddress: DirectMemoryWrite) {
val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
when {
addressLv != null -> {
asmgen.out(" lda $ldaInstructionArg | sta ${addressLv.number.toHex()}")
}
addressExpr is IdentifierReference -> {
asmgen.storeByteIntoPointer(addressExpr, ldaInstructionArg)
}
else -> {
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.out(" ldy #0 | lda $ldaInstructionArg | sta (P8ZP_SCRATCH_W2),y")
fun storeViaExprEval() {
when(addressExpr) {
is NumericLiteralValue, is IdentifierReference -> {
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
}
else -> {
// same as above but we need to save the A register
asmgen.out(" pha")
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.out(" pla")
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
}
}
}
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
val sourceName = asmgen.asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program.namespace)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
asmgen.out(" sta ($sourceName)")
} else {
asmgen.out("""
ldy $sourceName
sty P8ZP_SCRATCH_W2
ldy $sourceName+1
sty P8ZP_SCRATCH_W2+1
sta (P8ZP_SCRATCH_W2)""")
}
} else {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
asmgen.out(" ldy #0 | sta ($sourceName),y")
} else {
asmgen.out("""
ldy $sourceName
sty P8ZP_SCRATCH_W2
ldy $sourceName+1
sty P8ZP_SCRATCH_W2+1
ldy #0
sta (P8ZP_SCRATCH_W2),y""")
}
}
}
}
private fun storeRegisterInMemoryAddress(register: CpuRegister, memoryAddress: DirectMemoryWrite) {
// this is optimized for register A.
val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
val registerName = register.name.toLowerCase()
when {
addressLv != null -> {
asmgen.out(" st$registerName ${addressLv.number.toHex()}")
asmgen.out(" sta ${addressLv.number.toHex()}")
}
addressExpr is IdentifierReference -> {
when (register) {
CpuRegister.A -> {}
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
asmgen.storeByteIntoPointer(addressExpr, null)
storeAIntoPointerVar(addressExpr)
}
else -> {
asmgen.saveRegister(register, false, memoryAddress.definingSubroutine()!!)
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.restoreRegister(CpuRegister.A, false)
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
addressExpr is BinaryExpression -> {
if(!asmgen.tryOptimizedPointerAccessWithA(addressExpr, true))
storeViaExprEval()
}
else -> storeViaExprEval()
}
}

View File

@ -850,8 +850,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ldy #>$value
jsr math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
ldy P8ZP_SCRATCH_W2+1
sta $name
lda P8ZP_SCRATCH_W2+2
sty $name+1
""")
}
@ -1417,7 +1417,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: Expression, scope: Subroutine) {
asmgen.assignExpressionToRegister(value, RegisterOrPair.FAC1)
asmgen.saveRegister(CpuRegister.X, false, scope)
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
"**" -> {
asmgen.out("""
@ -1463,7 +1463,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ldy #>$name
jsr floats.MOVMF
""")
asmgen.restoreRegister(CpuRegister.X, false)
asmgen.restoreRegisterLocal(CpuRegister.X)
}
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference, scope: Subroutine) {
@ -1472,7 +1472,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
throw AssemblyError("float variable expected")
val otherName = asmgen.asmVariableName(ident)
asmgen.saveRegister(CpuRegister.X, false, scope)
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
"**" -> {
if(CompilationTarget.instance is Cx16Target) {
@ -1545,12 +1545,12 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ldy #>$name
jsr floats.MOVMF
""")
asmgen.restoreRegister(CpuRegister.X, false)
asmgen.restoreRegisterLocal(CpuRegister.X)
}
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: Subroutine) {
val constValueName = asmgen.getFloatAsmConst(value)
asmgen.saveRegister(CpuRegister.X, false, scope)
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
"**" -> {
if(CompilationTarget.instance is Cx16Target) {
@ -1630,7 +1630,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ldy #>$name
jsr floats.MOVMF
""")
asmgen.restoreRegister(CpuRegister.X, false)
asmgen.restoreRegisterLocal(CpuRegister.X)
}
private fun inplaceCast(target: AsmAssignTarget, cast: TypecastExpression, position: Position) {
@ -1652,10 +1652,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
TargetStorageKind.ARRAY -> {
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, target.datatype, CpuRegister.Y, true)
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
asmgen.out(" stz ${target.asmVarname},y")
else
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
}
TargetStorageKind.STACK -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)

View File

@ -21,7 +21,6 @@ internal object CX16MachineDefinition: IMachineDefinition {
override val RAW_LOAD_ADDRESS = 0x8000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above
override val ESTACK_LO = 0x0400 // $0400-$04ff inclusive
override val ESTACK_HI = 0x0500 // $0500-$05ff inclusive
@ -29,7 +28,6 @@ internal object CX16MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
override fun getFloatRomConst(number: Double): String? = null // Cx16 has no pulblic ROM float locations
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib")
@ -73,8 +71,8 @@ internal object CX16MachineDefinition: IMachineDefinition {
internal class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x79 // temp storage for a single byte
override val SCRATCH_REG = 0x7a // temp storage for a register, must be B1+1
override val SCRATCH_B1 = 0x7a // temp storage for a single byte
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_W2 = 0x7e // temp storage 2 for a word $7e+$7f

View File

@ -102,6 +102,7 @@ private val functionSignatures: List<FSignature> = listOf(
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
FSignature("offsetof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinOffsetof),
// normal functions follow:
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
@ -133,44 +134,9 @@ private val functionSignatures: List<FSignature> = listOf(
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
FSignature("rndw" , false, emptyList(), DataType.UWORD),
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
FSignature("exit" , false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
FSignature("rsave" , false, emptyList(), null),
FSignature("rrestore" , false, emptyList(), null),
FSignature("set_carry" , false, emptyList(), null),
FSignature("clear_carry" , false, emptyList(), null),
FSignature("set_irqd" , false, emptyList(), null),
FSignature("clear_irqd" , false, emptyList(), null),
FSignature("read_flags" , false, emptyList(), DataType.UBYTE),
FSignature("progend" , true, emptyList(), DataType.UWORD),
FSignature("memory" , true, listOf(FParam("name", setOf(DataType.STR)), FParam("size", setOf(DataType.UWORD))), DataType.UWORD),
FSignature("swap" , false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
FSignature("memcopy" , false, listOf(
FParam("from", IterableDatatypes + DataType.UWORD),
FParam("to", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("memset" , false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UWORD)),
FParam("bytevalue", ByteDatatypes)), null),
FSignature("memsetw" , false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numwords", setOf(DataType.UWORD)),
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
FSignature("strlen" , true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen),
FSignature("strcopy" , false, listOf(FParam("from", IterableDatatypes + DataType.UWORD), FParam("to", IterableDatatypes + DataType.UWORD)), DataType.UBYTE),
FSignature("substr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("start", setOf(DataType.UBYTE)),
FParam("length", setOf(DataType.UBYTE))), null),
FSignature("leftstr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
FSignature("rightstr" , false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
FSignature("strcmp" , false, listOf(FParam("s1", IterableDatatypes + DataType.UWORD), FParam("s2", IterableDatatypes + DataType.UWORD)), DataType.BYTE, null)
)
val BuiltinFunctions = functionSignatures.associateBy { it.name }
@ -313,6 +279,28 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
}
}
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program): 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.namespace)!!
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 += member.datatype.memorySize()
}
throw SyntaxError("undefined struct member", position)
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
@ -349,23 +337,6 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
}
}
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
val argument=args[0]
if(argument is StringLiteralValue)
return NumericLiteralValue.optimalInteger(argument.value.length, argument.position)
val vardecl = (argument as? IdentifierReference)?.targetVarDecl(program.namespace)
if(vardecl!=null) {
if(vardecl.datatype!=DataType.STR && vardecl.datatype!=DataType.UWORD)
throw SyntaxError("strlen must have string argument", position)
if(vardecl.autogeneratedDontRemove && vardecl.value!=null) {
return NumericLiteralValue.optimalInteger((vardecl.value as StringLiteralValue).value.length, argument.position)
}
}
throw NotConstArgumentException()
}
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)

View File

@ -117,12 +117,9 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
override fun visit(decl: VarDecl) {
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
if (decl.autogeneratedDontRemove || decl.datatype==DataType.STRUCT)
addNodeAndParentScopes(decl)
}
if (decl.datatype == DataType.STRUCT)
else if(decl.parent is Block && decl.definingModule().isLibraryModule)
addNodeAndParentScopes(decl)
super.visit(decl)

View File

@ -136,21 +136,25 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
}
}
val subExpr: BinaryExpression? = when {
leftconst!=null -> expr.right as? BinaryExpression
rightconst!=null -> expr.left as? BinaryExpression
else -> null
}
if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(program)
val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
// try reordering.
val change = groupTwoConstsTogether(expr, subExpr,
if(expr.inferType(program).istype(DataType.FLOAT)) {
val subExpr: BinaryExpression? = when {
leftconst != null -> expr.right as? BinaryExpression
rightconst != null -> expr.left as? BinaryExpression
else -> null
}
if (subExpr != null) {
val subleftconst = subExpr.left.constValue(program)
val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst == null && subrightconst != null)) {
// try reordering.
val change = groupTwoFloatConstsTogether(
expr, subExpr,
leftconst != null, rightconst != null,
subleftconst != null, subrightconst != null)
if(change!=null)
modifications += change
subleftconst != null, subrightconst != null
)
if (change != null)
modifications += change
}
}
}
@ -226,7 +230,8 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)!!
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace) ?: throw UndefinedSymbolError(forLoop.loopVar)
val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) {
DataType.UBYTE -> {
@ -297,13 +302,15 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
}
}
private fun groupTwoConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression,
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
subrightIsConst: Boolean): IAstModification?
private fun groupTwoFloatConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression,
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
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
if(expr.operator==subExpr.operator) {
// both operators are the same.

View File

@ -13,18 +13,23 @@ import prog8.ast.statements.VarDecl
import prog8.compiler.target.CompilationTarget
// Fix up the literal value's type to match that of the vardecl
internal class VarConstantValueTypeAdjuster(private val program: Program) : AstWalker() {
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
try {
val declConstValue = decl.value?.constValue(program)
if(declConstValue!=null && (decl.type==VarDeclType.VAR || decl.type==VarDeclType.CONST)
&& !declConstValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
// cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype)
if(cast.isValid)
return listOf(IAstModification.ReplaceNode(decl.value!!, cast.valueOrZero(), decl))
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
}
return noModifications
}
}
@ -47,11 +52,22 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(forloop!=null && identifier===forloop.loopVar)
return noModifications
val cval = identifier.constValue(program) ?: return noModifications
return when (cval.type) {
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> noModifications
try {
val cval = identifier.constValue(program) ?: return noModifications
return when (cval.type) {
in NumericDatatypes -> listOf(
IAstModification.ReplaceNode(
identifier,
NumericLiteralValue(cval.type, cval.number, identifier.position),
identifier.parent
)
)
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> noModifications
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
return noModifications
}
}
@ -78,11 +94,16 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
}
} else if(arraysize.constIndex()==null) {
// see if we can calculate the size from other fields
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
if(cval!=null) {
arraysize.indexVar = null
arraysize.origExpression = null
arraysize.indexNum = cval
try {
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
if (cval != null) {
arraysize.indexVar = null
arraysize.origExpression = null
arraysize.indexNum = cval
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
return noModifications
}
}
}

View File

@ -669,12 +669,15 @@ internal class ExpressionSimplifier(private val program: Program) : AstWalker()
DataType.UWORD -> {
if (amount >= 16) {
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)
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 -> {

View File

@ -5,7 +5,7 @@ import prog8.ast.base.ErrorReporter
internal fun Program.constantFold(errors: ErrorReporter) {
val valuetypefixer = VarConstantValueTypeAdjuster(this)
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this)
if(errors.isEmpty()) {
valuetypefixer.applyModifications()

View File

@ -39,7 +39,7 @@ internal class StatementOptimizer(private val program: Program,
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.containsNoCodeNorVars()) {
if(subroutine.containsNoCodeNorVars() && !subroutine.inline) {
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
val removals = callgraph.calledBy.getValue(subroutine).map {
IAstModification.Remove(it, it.definingScope())
@ -441,6 +441,47 @@ internal class StatementOptimizer(private val program: Program,
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 {
class Searcher: IAstVisitor

View File

@ -4,6 +4,7 @@ import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
@ -93,7 +94,14 @@ internal class UnusedCodeRemover(private val program: Program, private val error
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
linesToRemove.add(assign1)
// 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

@ -100,7 +100,51 @@ internal class ModuleImporter {
return moduleAst
}
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null
val rsc = tryGetModuleFromResource("$moduleName.p8")
val importedModule =
if(rsc!=null) {
// load the module from the embedded resource
val (resource, resourcePath) = rsc
resource.use {
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"), true)
}
} else {
val modulePath = tryGetModuleFromFile(moduleName, source, import.position)
importModule(program, modulePath)
}
importedModule.checkImportedValid()
return importedModule
}
private fun tryGetModuleFromResource(name: String): Pair<InputStream, String>? {
val target = CompilationTarget.instance.name
val targetSpecificPath = "/prog8lib/$target/$name"
val targetSpecificResource = object{}.javaClass.getResourceAsStream(targetSpecificPath)
if(targetSpecificResource!=null)
return Pair(targetSpecificResource, targetSpecificPath)
val generalPath = "/prog8lib/$name"
val generalResource = object{}.javaClass.getResourceAsStream(generalPath)
if(generalResource!=null)
return Pair(generalResource, generalPath)
return null
}
private fun tryGetModuleFromFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8"
val locations = if(source.toString().isEmpty()) mutableListOf<Path>() else mutableListOf(source.parent ?: Path.of("."))
@ -119,48 +163,4 @@ internal class ModuleImporter {
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: embedded libs and $locations)")
}
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null
val rsc = tryGetEmbeddedResource("$moduleName.p8")
val importedModule =
if(rsc!=null) {
// load the module from the embedded resource
val (resource, resourcePath) = rsc
resource.use {
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$resourcePath"), true)
}
} else {
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
importModule(program, modulePath)
}
importedModule.checkImportedValid()
return importedModule
}
private fun tryGetEmbeddedResource(name: String): Pair<InputStream, String>? {
val target = CompilationTarget.instance.name
val targetSpecificPath = "/prog8lib/$target/$name"
val targetSpecificResource = object{}.javaClass.getResourceAsStream(targetSpecificPath)
if(targetSpecificResource!=null)
return Pair(targetSpecificResource, targetSpecificPath)
val generalPath = "/prog8lib/$name"
val generalResource = object{}.javaClass.getResourceAsStream(generalPath)
if(generalResource!=null)
return Pair(generalResource, generalPath)
return null
}
}

View File

@ -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

@ -477,7 +477,7 @@ class TestMemory {
val memexpr = IdentifierReference(listOf("address"), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
return target
}
@ -497,7 +497,7 @@ class TestMemory {
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@ -509,7 +509,7 @@ class TestMemory {
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@ -521,7 +521,7 @@ class TestMemory {
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertFalse(target.isInRegularRAM(target.definingScope()))
}
@ -533,7 +533,7 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@ -546,7 +546,7 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
}
@ -559,7 +559,7 @@ class TestMemory {
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, mutableListOf(decl, assignment), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertFalse(target.isInRegularRAM(target.definingScope()))
}

View File

@ -0,0 +1,96 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm" version "1.4.21"
id 'com.github.johnrengelman.shadow' version '6.1.0'
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenLocal()
mavenCentral()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
implementation "com.github.hypfvieh:dbus-java:3.2.4"
implementation "org.slf4j:slf4j-simple:1.7.30"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
}
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
}
}
}
startScripts.enabled = true
application {
mainClass = 'prog8.dbus.TestdbusKt'
mainClassName = 'prog8.dbus.TestdbusKt' // deprecated
applicationName = 'testdbus'
}
artifacts {
archives shadowJar
}
shadowJar {
archiveBaseName = 'prog8compilerservicedbus'
archiveVersion = '1.0'
// minimize()
}
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "skipped", "failed"
}
}
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$/../dbusCompilerService">
<sourceFolder url="file://$MODULE_DIR$/../dbusCompilerService/src" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
<orderEntry type="library" name="dbus-java-3.2.4" level="project" />
</component>
</module>

Binary file not shown.

View File

@ -0,0 +1,22 @@
package prog8.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

@ -0,0 +1,17 @@
package prog8.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

@ -0,0 +1,18 @@
package prog8.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

@ -10,10 +10,10 @@ Prog8 documentation - |version|
What is Prog8?
--------------
This is an experimental compiled programming language targeting the 8-bit
This is a compiled programming language targeting the 8-bit
`6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ /
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ microprocessor.
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
This CPU is from the late 1970's and early 1980's and was used in many home computers from that era,
such as the `Commodore-64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
The language aims to provide many conveniences over raw assembly code (even when using a macro assembler),
@ -55,7 +55,8 @@ Language features
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
- Variable data types include signed and unsigned bytes and words, arrays, strings and floats.
- High-level code optimizations, such as const-folding, expression and statement simplifications/rewriting.
- Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``substr``, ``sort`` and ``reverse`` (and others)
- Many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``sort`` and ``reverse``
- Supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, also on the C64.
- 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)!
@ -73,7 +74,7 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
sub start() {
; clear the sieve, to reset starting situation on subsequent runs
memset(sieve, 256, false)
sys.memset(sieve, 256, false)
; calculate primes
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0
@ -85,10 +86,10 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
txt.print(", ")
amount++
}
txt.chrout('\n')
txt.nl()
txt.print("number of primes (expected 54): ")
txt.print_ub(amount)
txt.chrout('\n')
txt.nl()
}
sub find_next_prime() -> ubyte {
@ -132,7 +133,7 @@ Required tools
`64tass <https://sourceforge.net/projects/tass64/>`_ - cross assembler. Install this on your shell path.
It's very easy to compile yourself.
A recent precompiled .exe for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
A recent precompiled .exe (only for Windows) can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
*You need at least version 1.55.2257 of this assembler to correctly use the breakpoints feature.*
It's possible to use older versions, but it is very likely that the automatic Vice breakpoints won't work with them.
@ -165,6 +166,7 @@ If you're targeting the CommanderX16 instead, there's the `x16emu <https://githu
programming.rst
syntaxreference.rst
libraries.rst
technical.rst
todo.rst

View File

@ -16,6 +16,10 @@ with Prog8 are written like this.
You can ``%import`` and use these modules explicitly, but the compiler may also import one or more
of these library modules automatically as required.
For full details on what is available in the libraries, look at their source code here:
https://github.com/irmen/prog8/tree/master/compiler/res/prog8lib
.. caution::
The resulting compiled binary program *only works on the target machine it was compiled for*.
You must recompile the program for every target you want to run it on.
@ -30,6 +34,72 @@ as ROM/kernal subroutine definitions, memory location constants, and utility sub
Many of these definitions overlap for the C64 and Commander X16 targets so it is still possible
to write programs that work on both targets without modifications.
sys (part of syslib)
--------------------
``target``
A constant ubyte value designating the target machine that the program is compiled for.
Notice that this is a compile-time constant value and is not determined on the
system when the program is running.
The following return values are currently defined:
- 16 = compiled for CommanderX16 with 65C02 CPU
- 64 = compiled for Commodore-64 with 6502/6510 CPU
``exit(returncode)``
Immediately stops the program and exits it, with the returncode in the A register.
Note: custom interrupt handlers remain active unless manually cleared first!
``memcopy(from, to, numbytes)``
Efficiently copy a number of bytes from a memory location to another.
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not.
The most efficient will often be to write a specialized copy routine in assembly yourself!
``memset(address, numbytes, bytevalue)``
Efficiently set a part of memory to the given (u)byte value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
Note that for clearing the screen, very fast specialized subroutines are
available in the ``textio`` and ``graphics`` library modules.
``memsetw(address, numwords, wordvalue)``
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
``rsave()``
Saves the CPU registers and the status flags.
You can now more or less 'safely' use the registers directly, until you
restore them again so the generated code can carry on normally.
Note: it's not needed to rsave() before an asm subroutine that clobbers the X register
(which is used as the internal evaluation stack pointer).
The compiler will take care of this situation automatically.
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* saved.
``rrestore()``
Restores the CPU registers and the status flags from previously saved values.
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* restored.
``read_flags() -> ubyte``
Returns the current value of the CPU status register.
``set_carry()``
Sets the CPU status register Carry flag.
``clear_carry()``
Clears the CPU status register Carry flag.
``set_irqd()``
Sets the CPU status register Interrupt Disable flag.
``clear_irqd()``
Clears the CPU status register Interrupt Disable flag.
``progend()``
Returns the last address of the program in memory + 1.
Can be used to load dynamic data after the program, instead of hardcoding something.
conv
----
Routines to convert strings to numbers or vice versa.
@ -54,13 +124,62 @@ diskio
------
Provides several routines that deal with disk drive I/O, such as:
- list files on disk, optionally filtering by prefix or suffix
- list files on disk, optionally filtering by a simple pattern with ? and *
- show disk directory as-is
- display disk drive status
- load and save data from and to the disk
- delete and rename files on the disk
string
------
Provides string manipulation routines.
``length(str) -> ubyte length``
Number of bytes in the string. This value is determined during runtime and counts upto
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
Don't confuse this with ``len`` and ``sizeof``
``left(source, length, target)``
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, doesn't return a value (so can't be used in an expression).
``right(source, length, target)``
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, doesn't return a value (so can't be used in an expression).
``slice(source, start, length, target)``
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, doesn't return a value (so can't be used in an expression).
``find(string, char) -> uword address``
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.
``compare(string1, string2) -> ubyte result``
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
Note that you can also directly compare strings and string values with eachother
using ``==``, ``<`` etcetera (it will use string.compare for you under water automatically).
``copy(from, to) -> ubyte length``
Copy a string to another, overwriting that one. Returns the length of the string that was copied.
Often you don't have to call this explicitly and can just write ``string1 = string2``
but this function is useful if you're dealing with addresses for instance.
``lower(string)``
Lowercases the petscii-string in place.
``upper(string)``
Uppercases the petscii-string in place.
floats
------
Provides definitions for the ROM/kernel subroutines and utility routines dealing with floating
@ -69,12 +188,36 @@ point variables. This includes ``print_f``, the routine used to print floating
graphics
--------
High-res monochrome bitmap graphics routines:
Monochrome bitmap graphics routines, fixed 320*200 resolution:
- clearing the screen
- drawing lines
- drawing circles and discs (filled circles)
- plotting individual pixels
- drawing individual pixels
- drawing lines, rectangles, filled rectangles, circles, discs
This library is available both on the C64 and the Cx16.
It uses the ROM based graphics routines on the latter, and it is a very small library because of that.
That also means though that it is constrained to 320*200 resolution on the Cx16 as well.
Use the ``gfx2`` library if you want full-screen graphics or non-monochrome drawing.
gfx2 (cx16 only)
-----------------
Full-screen multicolor bitmap graphics routines, available on the Cx16 machine only.
- multiple full-screen resolutions: 640 * 480 monochrome, and 320 * 240 monochrome and 256 colors
- clearing screen, switching screen mode, also back to text mode is possible.
- drawing individual pixels
- drawing lines, rectangles, filled rectangles, circles, discs
- drawing text inside the bitmap
- in monochrome mode, it's possible to use a stippled drawing pattern to simulate a shade of gray.
palette (cx16 only)
--------------------
Available for the Cx16 target. Various routines to set the display color palette.
There are also a few better looking Commodore-64 color palettes available here,
because the Commander X16's default colors for this (the first 16 colors) are too saturated
and are quite different than how they looked on a VIC-II chip in a C-64.
math
@ -89,15 +232,6 @@ A 'fun' module that contains the Commander X16 logo and that allows you
to print it anywhere on the screen.
c64colors
---------
Available for the CommanderX16 target, a module that contains a few better
color palettes for how the colors of the VIC-II looked on the Commodore-64.
There are subroutines to activate one of the several palettes of your liking.
The Commander X16's default colors for this (the first 16 colors) are too saturated
and are quite different than how a C-64 looked.
prog8_lib
---------
Low level language support. You should not normally have to bother with this directly.

View File

@ -376,6 +376,26 @@ address you specified, and setting the varible will directly modify that memory
&word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
Direct access to memory locations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Normally memory locations are accessed by a *memory mapped* name, such as ``c64.BGCOL0`` that is defined
as the memory mapped address $d021.
If you want to access a memory location directly (by using the address itself or via an uword pointer variable),
without defining a memory mapped location, you can do so by enclosing the address in ``@(...)``::
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
@(vic+$20) = 6 ; you can also use expressions to 'calculate' the address
This is the official syntax to 'dereference a pointer' as it is often named in other languages.
You can actually also use the array indexing notation for this. It will be silently converted into
the direct memory access expression as explained above. Note that this also means that unlike regular arrays,
the index is not limited to an ubyte value. You can use a full uword to index a pointer variable like this::
pointervar[999] = 0 ; set memory byte to zero at location pointervar + 999.
Converting types into other types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -481,6 +501,12 @@ condition meaning
So ``if_cc goto target`` will directly translate into the single CPU instruction ``BCC target``.
.. caution::
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
that the status register (still) contains the correct status bits.
This is not always the case after a fuction call or other operations!
If in doubt, check the generated assembly code!
.. note::
For now, the symbols used or declared in the statement block(s) are shared with
the same scope the if statement itself is in.
@ -530,18 +556,6 @@ a fixed amount of memory which will not change. (You *can* change the value of
that there is a loss of precision. You can use builtin functions such as ``round`` and ``lsb`` to convert
to a smaller datatype, or revert to integer arithmetic.
Direct access to memory locations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Normally memory locations are accessed by a *memory mapped* name, such as ``c64.BGCOL0`` that is defined
as the memory mapped address $d021.
If you want to access a memory location directly (by using the address itself), without defining
a memory mapped location, you can do so by enclosing the address in ``@(...)``::
color = @($d020) ; set the variable 'color' to the current c64 screen border color ("peek(53280)")
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
@(vic+$20) = 6 ; you can also use expressions to 'calculate' the address
Expressions
-----------
@ -625,6 +639,13 @@ Defining a subroutine
Subroutines are parts of the code that can be repeatedly invoked using a subroutine call from elsewhere.
Their definition, using the ``sub`` statement, includes the specification of the required parameters and return value.
Subroutines can be defined in a Block, but also nested inside another subroutine. Everything is scoped accordingly.
With ``asmsub`` you can define a low-level subroutine that is implemented in inline assembly and takes any parameters
in registers directly.
Trivial subroutines can be tagged as inline to tell the compiler to copy their code
in-place to the locations where the subroutine is called, rather than inserting an actual call and return to the
subroutine. This may increase code size significantly and can only be used in limited scenarios, so YMMV.
Calling a subroutine
^^^^^^^^^^^^^^^^^^^^
@ -765,67 +786,8 @@ sort(array)
Sorting strings alphabetically has to be programmed yourself if you need it.
Strings and memory blocks
^^^^^^^^^^^^^^^^^^^^^^^^^
memcopy(from, to, numbytes)
Efficiently copy a number of bytes from a memory location to another.
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not.
The most efficient will often be to write a specialized copy routine in assembly yourself!
memset(address, numbytes, bytevalue)
Efficiently set a part of memory to the given (u)byte value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
Note that for clearing the screen, very fast specialized subroutines are
available in the ``textio`` and ``graphics`` library modules.
memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
leftstr(source, target, length)
Copies the left side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rightstr(source, target, length)
Copies the right side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
Modifies in-place, doesn't return a value (so can't be used in an expression).
strlen(str)
Number of bytes in the string. This value is determined during runtime and counts upto
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
Don't confuse this with ``len`` and ``sizeof``
strcmp(string1, string2)
Returns -1, 0 or 1 depeding on wether string1 sorts before, equal or after string2.
Note that you can also directly compare strings and string values with eachother
using ``==``, ``<`` etcetera (it will use strcmp for you under water automatically).
substr(source, target, start, length)
Copies a segment from the source string, starting at the given index,
and of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Also, you have to make sure yourself that start and length are within bounds of the strings.
Modifies in-place, doesn't return a value (so can't be used in an expression).
strcopy(from, to)
Copy a string to another, overwriting that one. Returns the length of the string that was copied.
Often you don't have to call this explicitly and can just write ``string1 = string2``
but this function is useful if you're dealing with addresses for instance.
Miscellaneous
^^^^^^^^^^^^^
exit(returncode)
Immediately stops the program and exits it, with the returncode in the A register.
Note: custom interrupt handlers remain active unless manually cleared first!
lsb(x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
@ -875,40 +837,30 @@ ror2(x)
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rsave()
Saves the CPU registers and the status flags.
You can now more or less 'safely' use the registers directly, until you
restore them again so the generated code can carry on normally.
Note: it's not needed to rsave() before an asm subroutine that clobbers the X register
(which is used as the internal evaluation stack pointer).
The compiler will take care of this situation automatically.
rrestore()
Restores the CPU registers and the status flags from previously saved values.
read_flags()
Returns the current value of the CPU status register.
sizeof(name)
Number of bytes that the object 'name' occupies in memory. This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C-64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
set_carry() / clear_carry()
Set (or clear) the CPU status register Carry flag. No result value.
(translated into ``SEC`` or ``CLC`` cpu instruction)
set_irqd() / clear_irqd()
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into ``SEI`` or ``CLI`` cpu instruction)
offsetof(membername)
Number of bytes from the start of a struct variable that this member variable is located.
For now, this only works on members of a declared struct variable and not yet on members
referenced from the struct type itself. This might be improved in a future version of the language.
swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way.
progend()
Returns the last address of the program in memory + 1.
Can be used to load dynamic data after the program, instead of hardcoding something.
memory(name, size)
Returns the address of the first location of a statically "reserved" block of memory of the given size in bytes,
with the given name. Requesting the address of such a named memory block again later with
the same name, will result in the same address as before.
When reusing blocks in that way, it is required that the size argument is the same,
otherwise you'll get a compilation error.
This routine can be used to "reserve" parts of the memory where a normal byte array variable would
not suffice; for instance if you need more than 256 consecutive bytes.
The return value is just a simple uword address so it cannot be used as an array in your program.
You can only treat it as a pointer or use it in inline assembly.
Library routines

View File

@ -335,6 +335,10 @@ directly access the memory. Enclose a numeric expression or literal with ``@(...
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
@(vic+$20) = 6 ; a dynamic expression to 'calculate' the address
The array indexing notation is syntactic sugar for such a direct memory access expression::
pointervar[999] = 0 ; equivalent to @(pointervar+999) = 0
Constants
^^^^^^^^^
@ -517,11 +521,11 @@ You can still call the subroutine and not store the results.
**There is an exception:** if there's just one return value in a register, and one or more others that are returned
as bits in the status register (such as the Carry bit), the compiler allows you to call the subroutine.
It will then store the result value in a variable if required, and *keep the status register untouched
after the call* so you can use a conditional branch statement for that.
Note that this makes no sense inside an expression, so the compiler will still give an error for that.
It will then store the result value in a variable if required, and *try to keep the status register untouched
after the call* so you can often use a conditional branch statement for that. But the latter is tricky,
make sure you check the generated assembly code.
If there really are multiple return values (other than a combined 16 bit return value in 2 registers),
If there really are multiple relevant return values (other than a combined 16 bit return value in 2 registers),
you'll have to write a small block of custom inline assembly that does the call and stores the values
appropriately. Don't forget to save/restore any registers that are modified.
@ -531,7 +535,7 @@ Subroutine definitions
The syntax is::
sub <identifier> ( [parameters] ) [ -> returntype ] {
[inline] sub <identifier> ( [parameters] ) [ -> returntype ] {
... statements ...
}
@ -544,6 +548,9 @@ The open curly brace must immediately follow the subroutine result specification
and can have nothing following it. The close curly brace must be on its own line as well.
The parameters is a (possibly empty) comma separated list of "<datatype> <parametername>" pairs specifying the input parameters.
The return type has to be specified if the subroutine returns a value.
The ``inline`` keyword makes their code copied in-place to the locations where the subroutine is called,
rather than having an actual call and return to the subroutine. This is meant for trivial subroutines only
as it can increase code size significantly.
Assembly / ROM subroutines
@ -557,7 +564,6 @@ This defines the ``LOAD`` subroutine at ROM memory address $FFD5, taking argumen
and returning stuff in several registers as well. The ``clobbers`` clause is used to signify to the compiler
what CPU registers are clobbered by the call instead of being unchanged or returning a meaningful result value.
User subroutines in the program source code that are implemented purely in assembly and which have an assembly calling convention (i.e.
the parameters are strictly passed via cpu registers), are defined with ``asmsub`` like this::
@ -576,6 +582,26 @@ the parameters are strictly passed via cpu registers), are defined with ``asmsub
the statement body of such a subroutine should consist of just an inline assembly block.
The ``@ <register>`` part is required for rom and assembly-subroutines, as it specifies for the compiler
what cpu registers should take the routine's arguments. You can use the regular set of registers
(A, X, Y), the special 16-bit register pairs to take word values (AX, AY and XY) and even a processor status
flag such as Carry (Pc).
.. note::
Asmsubs can also be tagged as ``inline asmsub`` to make trivial pieces of assembly inserted
directly instead of a call to them. Note that it is literal copy-paste of code that is done,
so make sure the assembly is actually written to behave like such - which probably means you
don't want a ``rts`` or ``jmp`` in it!
.. note::
The 'virtual' 16-bit registers from the Commander X16 can also be used as ``R0`` .. ``R15`` .
This means you don't have to set them up manually before calling a subroutine that takes
one or more parameters in those 'registers'. You can just list the arguments directly.
*This also works on the Commodore-64!* (however they are not as efficient there because they're not in zeropage)
In prog8 and assembly code these 'registers' are directly accessible too via
``cx16.r0`` .. ``cx16.r15`` (they're memory mapped uword values)
Expressions
-----------
@ -721,6 +747,13 @@ The XX corresponds to one of the eigth branching instructions so the possibiliti
``if_cs``, ``if_cc``, ``if_eq``, ``if_ne``, ``if_pl``, ``if_mi``, ``if_vs`` and ``if_vc``.
It can also be one of the four aliases that are easier to read: ``if_z``, ``if_nz``, ``if_pos`` and ``if_neg``.
.. caution::
These special ``if_XX`` branching statements are only useful in certain specific situations where you are *certain*
that the status register (still) contains the correct status bits.
This is not always the case after a fuction call or other operations!
If in doubt, check the generated assembly code!
when statement ('jump table')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The structure of a when statement is like this::

View File

@ -11,8 +11,8 @@ Prog8 targets the following hardware:
Currently there are two machines that are supported as compiler target (selectable via the ``-target`` compiler argument):
- 'c64': the well-known Commodore-64, premium support
- 'cx16': the `CommanderX16 <https://www.commanderx16.com/>`_ a project from the 8-Bit Guy. Support for this is still experimental.
- 'c64': the well-known Commodore-64
- 'cx16': the `CommanderX16 <https://www.commanderx16.com/>`_ conceived by the 8-Bit Guy.
This chapter explains the relevant system details of these machines.
@ -131,21 +131,8 @@ The status register (P) carry flag and interrupt disable flag can be written via
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
and read via the ``read_flags()`` function.
Subroutine Calling Conventions
------------------------------
**Kernel/assembly subroutines:**
Arguments and results are passed via registers.
Sometimes the status register's Carry flag is used as well (as a boolean flag).
Special care should be taken when the subroutine clobbers the X register.
If it does, X must be saved before and restored after the call.
**Normal user defined subroutines:**
Arguments and result values are passed via global variables stored in memory
*These are not allocated on a stack* so it is not possible to create recursive calls!
The result value(s) of a subroutine are returned on the evaluation stack,
to make it possible to use subroutines in expressions.
The 16 'virtual' 16-bit registers that are defined on the Commander X16 machine are not real hardware
registers and are just 16 memory-mapped word values that you *can* access directly.
IRQ Handling

85
docs/source/technical.rst Normal file
View File

@ -0,0 +1,85 @@
===============
Technical stuff
===============
All variables are static in memory
----------------------------------
All variables are allocated statically, there is no concept of dynamic heap or stack frames.
Essentially all variables are global (but scoped) and can be accessed and modified anywhere,
but care should be taken ofcourse to avoid unexpected side effects.
Especially when you're dealing with interrupts or re-entrant routines: don't modify variables
that you not own or else you will break stuff.
Software stack for expression evaluation
----------------------------------------
Prog8 uses a software stack to evaluate complex expressions that it can't calculate in-place or
directly into the target variable, register, or memory location.
'software stack' means: seperated and not using the processor's hardware stack.
The software stack is implemented as follows:
- 2 pages of memory are allocated for this, exact locations vary per machine target.
For the C-64 they are set at $ce00 and $cf00 (so $ce00-$cfff is reserved).
For the Commander X16 they are set at $0400 and $0500 (so $0400-$05ff are reserved).
- these are the high and low bytes of the values on the stack (it's a 'split 16 bit word stack')
- for byte values just the lsb page is used, for word values both pages
- float values (5 bytes) are chopped up into 2 words and 1 byte on this stack.
- the X register is permanently allocated to be the stack pointer in the software stack.
- you can use the X register as long as you're not using the software stack.
But you *must* make sure it is saved and restored after the code that modifies it,
otherwise the evaluation stack gets corrupted.
Subroutine Calling Convention
-----------------------------
Calling a subroutine requires three steps:
#. preparing the arguments (if any) and passing them to the routine
#. calling the routine
#. preparig the return value (if any) and returning that from the call.
Calling the routine is just a simple JSR instruction, but the other two work like this:
``asmsub`` routines
^^^^^^^^^^^^^^^^^^^
These are usually declarations of kernel (ROM) routines or low-level assembly only routines,
that have their arguments solely passed into specific registers.
Sometimes even via a processor status flag such as the Carry flag.
Return values also via designated registers.
The processor status flag is preserved on returning so you can immediately act on that for instance
via a special branch instruction such as ``if_z`` or ``if_cs`` etc.
regular subroutines
^^^^^^^^^^^^^^^^^^^
- subroutine parameters are just variables scoped to the subroutine.
- the arguments passed in a call are evaluated (using the eval-stack if needed) and then
copied into those variables.
This sometimes can seem inefficient but it's required to allow subroutines to work locally
with their parameters and allow them to modify them as required, without changing the
variables used in the call's arguments. If you want to get rid of this overhead you'll
have to make an ``asmsub`` routine in assembly instead.
- the return value is passed back to the caller via cpu register(s):
Byte values will be put in ``A`` .
Word values will be put in ``A`` + ``Y`` register pair.
Float values will be put in the ``FAC1`` float 'register' (Basic allocated this somewhere in ram).
Calls to builtin functions are treated in a special way:
Generally if they have a single argument it's passed in a register or register pair.
Multiple arguments are passed like a normal subroutine, into variables.
Some builtin functions have a fully custom implementation.
The compiler will warn about routines that are called and that return a value, if you're not
doing something with that returnvalue. This can be on purpuse if you're simply not interested in it.
Use the ``void`` keyword in front of the subroutine call to get rid of the warning in that case.

View File

@ -2,12 +2,23 @@
TODO
====
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
- Cx16 target: support full-screen 640x480 and 320x240 graphics? That requires our own custom graphics routines though to draw lines.
- add any2(), all2(), max2(), min2(), reverse2(), sum2(), sort2() that take (array, startindex, length) arguments
- optimize for loop iterations better to allow proper inx, cpx #value, bne loop instructions (like repeat loop)
- why is there a beq _prog8_label_2_repeatend at the end of repeat loops? seems unused
- optimize swap of two memread values with index, using the same pointer expression/variable, like swap(@(ptr+1), @(ptr+2))
- implement the linked_list millfork benchmark
- use the 65c02 bit clear/set/test instructions for single-bit operations
- implement highres 4 color mode in gfx2
- make a retro amiga workbench 1.3 and/or 2.0 workbench "simulator" using that new gfx mode
- can we get rid of the --longOptionName command line options and only keep the short versions? https://github.com/Kotlin/kotlinx-cli/issues/50
- add a f_seek() routine for the Cx16 that uses its seek dos api?
- optimizer: detect variables that are written but never read - mark those as unused too and remove them, such as uword unused = memory("unused222", 20) - also remove the memory slab allocation
- add a compiler option to not remove unused subroutines. this allows for building library programs
- hoist all variable declarations up to the subroutine scope *before* even the constant folding takes place (to avoid undefined symbol errors when referring to a variable from another nested scope in the subroutine)
- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as '_'
- option to load the built-in library files from a directory instead of the embedded ones (for easier library development/debugging)
- use VIC banking to move up the graphics bitmap memory location. Move it to $e000 under the kernal rom?
- c64: make the graphics.BITMAP_ADDRESS configurable
- some support for recursive subroutines?
- via %option recursive?: allocate all params and local vars on estack, don't allow nested subroutines, can begin by first not allowing any local variables just fixing the parameters
- Or via a special recursive call operation that copies the current values of all local vars (including arguments) to the stack, replaces the arguments, jsr subroutine, and after returning copy the stack back to the local variables
@ -20,10 +31,8 @@ Add more compiler optimizations to the existing ones.
- further optimize assignment codegeneration, such as the following:
- binexpr splitting (beware self-referencing expressions and asm code ballooning though)
- detect var->var argument passing to subroutines and avoid the second variable and copying of the value
- more optimizations on the language AST level
- more optimizations on the final assembly source level
- note: subroutine inlining is abandoned because of problems referencing non-local stuff. Can't move everything around.
Eval stack redesign? (lot of work)

20
examples/.editorconfig Normal file
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

@ -1,5 +1,6 @@
%import floats
%import textio
%import string
%zeropage basicsafe
main {
@ -22,10 +23,10 @@ main {
if length!=5 txt.print("error len1\n")
length = len(uwarr)
if length!=5 txt.print("error len2\n")
length=strlen(name)
length=string.length(name)
if length!=5 txt.print("error strlen1\n")
name[3] = 0
length=strlen(name)
length=string.length(name)
if length!=3 txt.print("error strlen2\n")
; MAX

View File

@ -5,72 +5,72 @@
main {
sub start() {
ubyte A
ubyte a
txt.print("ubyte shift left\n")
A = shiftlb0()
txt.print_ubbin(A, true)
a = shiftlb0()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftlb1()
txt.print_ubbin(A, true)
a = shiftlb1()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftlb2()
txt.print_ubbin(A, true)
a = shiftlb2()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftlb3()
txt.print_ubbin(A, true)
a = shiftlb3()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftlb4()
txt.print_ubbin(A, true)
a = shiftlb4()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftlb5()
txt.print_ubbin(A, true)
a = shiftlb5()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftlb6()
txt.print_ubbin(A, true)
a = shiftlb6()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftlb7()
txt.print_ubbin(A, true)
a = shiftlb7()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftlb8()
txt.print_ubbin(A, true)
a = shiftlb8()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftlb9()
txt.print_ubbin(A, true)
a = shiftlb9()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
txt.print("enter to continue:\n")
void c64.CHRIN()
txt.print("ubyte shift right\n")
A = shiftrb0()
txt.print_ubbin(A, true)
a = shiftrb0()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftrb1()
txt.print_ubbin(A, true)
a = shiftrb1()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftrb2()
txt.print_ubbin(A, true)
a = shiftrb2()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftrb3()
txt.print_ubbin(A, true)
a = shiftrb3()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftrb4()
txt.print_ubbin(A, true)
a = shiftrb4()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftrb5()
txt.print_ubbin(A, true)
a = shiftrb5()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftrb6()
txt.print_ubbin(A, true)
a = shiftrb6()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftrb7()
txt.print_ubbin(A, true)
a = shiftrb7()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftrb8()
txt.print_ubbin(A, true)
a = shiftrb8()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
A = shiftrb9()
txt.print_ubbin(A, true)
a = shiftrb9()
txt.print_ubbin(a, true)
c64.CHROUT('\n')
txt.print("enter to continue:\n")
void c64.CHRIN()
@ -474,10 +474,6 @@ main {
return (q >> 17)
}
sub shiftrsw0() -> word {
word q = -12345
return q >> 0

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ main {
txt.plot(0,24)
ubyte Y
ubyte y
ubyte ub=200
byte bb=-100
uword uw = 2000
@ -33,9 +33,9 @@ main {
flarr[1] ++
check_ub(ub, 201)
Y=100
Y++
check_ub(Y, 101)
y=100
y++
check_ub(y, 101)
check_fl(fl, 1000.99)
check_b(bb, -99)
check_uw(uw, 2001)
@ -64,9 +64,9 @@ main {
flarr[1] --
check_ub(ub, 200)
Y=100
Y--
check_ub(Y, 99)
y=100
y--
check_ub(y, 99)
check_fl(fl, 999.99)
check_b(bb, -100)
check_uw(uw, 2000)

View File

@ -6,7 +6,7 @@
main {
sub start() {
repeat 25 {
txt.chrout('\n')
txt.nl()
}
ubyte ub
@ -25,7 +25,7 @@ main {
else {
txt.print("1 fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
@ -39,7 +39,7 @@ main {
else {
txt.print("2 fail:")
txt.print_w(wsum)
txt.chrout('\n')
txt.nl()
}
uwsum = 50000
@ -53,7 +53,7 @@ main {
else {
txt.print("3 fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
bb = 100
@ -90,7 +90,7 @@ main {
else {
txt.print("6 fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
@ -104,7 +104,7 @@ main {
else {
txt.print("7 fail:")
txt.print_w(wsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
@ -118,10 +118,10 @@ main {
else {
txt.print("8 fail:")
txt.print_w(wsum)
txt.chrout('\n')
txt.nl()
}
txt.chrout('\n')
txt.nl()
@ -135,7 +135,7 @@ main {
else {
txt.print("1b fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
txt.nl()
}
bb = 0
@ -148,7 +148,7 @@ main {
else {
txt.print("2b fail:")
txt.print_w(wsum)
txt.chrout('\n')
txt.nl()
}
uwsum = 50000
@ -160,7 +160,7 @@ main {
else {
txt.print("3b fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
wsum -= (100+bb)
@ -190,7 +190,7 @@ main {
else {
txt.print("6b fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
@ -202,7 +202,7 @@ main {
else {
txt.print("7b fail:")
txt.print_w(wsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
@ -214,10 +214,10 @@ main {
else {
txt.print("8b fail:")
txt.print_w(wsum)
txt.chrout('\n')
txt.nl()
}
txt.chrout('\n')
txt.nl()
@ -230,7 +230,7 @@ main {
else {
txt.print("1c fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
@ -242,7 +242,7 @@ main {
else {
txt.print("2c fail:")
txt.print_w(wsum)
txt.chrout('\n')
txt.nl()
}
uwsum = 50000
@ -254,7 +254,7 @@ main {
else {
txt.print("3c fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
wsum -= 100
@ -284,7 +284,7 @@ main {
else {
txt.print("6c fail:")
txt.print_uw(uwsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
@ -296,7 +296,7 @@ main {
else {
txt.print("7c fail:")
txt.print_w(wsum)
txt.chrout('\n')
txt.nl()
}
wsum = -30000
@ -308,7 +308,7 @@ main {
else {
txt.print("8c fail:")
txt.print_w(wsum)
txt.chrout('\n')
txt.nl()
}
}

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