Compare commits

...

98 Commits

Author SHA1 Message Date
markjreed
6f00a48772
fix: atan2(anything, 0) should return ±π/2 (#141)
* fix: atan2(anything, 0) should return pi/2

* fix: if y<0, x=0 maps to 3π/2, not π/2

* fix: standard seems to be atan2(0,0) == 0
2024-06-07 23:19:45 +02:00
Irmen de Jong
b3dba67405 added cx16.rom_version() routine 2024-06-07 23:15:26 +02:00
Irmen de Jong
c9a4235669 update to kotlin 2.0, fix several code style issues 2024-06-04 01:00:46 +02:00
Irmen de Jong
ae0d52274c Merge branch 'refs/heads/fixwindowseolstests' 2024-06-04 00:13:55 +02:00
Irmen de Jong
8973763866 Fix line endings conversion errors on windows builds 2024-06-04 00:12:12 +02:00
Irmen de Jong
3d799ae7fe todo 2024-06-01 15:03:01 +02:00
Irmen de Jong
8b10115390 release 10.3.1 2024-05-31 23:51:35 +02:00
Irmen de Jong
d2e010c439 added cx16.scnsiz (extapi call), describe profiler.py script 2024-05-31 21:48:29 +02:00
Irmen de Jong
15867ab423 update cx16.mouse_get() and mouse_pos() to also return scroll wheel in X 2024-05-29 23:19:53 +02:00
Irmen de Jong
22c9e99fa3 explain integer math sin/cos routines even better 2024-05-29 23:12:00 +02:00
Irmen de Jong
ee262f6aad explain integer math sin/cos routines even better 2024-05-29 20:26:42 +02:00
Irmen de Jong
af64af2397 explain integer math sin/cos routines better 2024-05-29 19:48:27 +02:00
Irmen de Jong
1feead2260 tweaks 2024-05-29 02:30:06 +02:00
Irmen de Jong
d3dcd24b4d add profiler script 2024-05-29 00:56:31 +02:00
Irmen de Jong
fd1e6796ef correct branch instruction, fixes #137 2024-05-24 20:54:40 +02:00
Irmen de Jong
3ea0f0cbaa remove 16 bit f_tell variant. 2024-05-22 21:47:02 +02:00
Irmen de Jong
f3e3311598 added diskio.f_tell() and f_tell32() on the cx16 target 2024-05-21 23:14:25 +02:00
Irmen de Jong
0dc50a93a4 added @nozp variable flag 2024-05-21 21:53:58 +02:00
Irmen de Jong
fda8e61be4 give better error when using @split wrong 2024-05-20 21:51:07 +02:00
Irmen de Jong
ac1d4b4a7a mouse_pos() now returns the coordinates as unsigned words 2024-05-20 21:38:02 +02:00
Irmen de Jong
c719e274d5 java version tweaks 2024-05-18 20:25:44 +02:00
Irmen de Jong
e4990f8ec5 Revert "update to Java 17 LTS"
This reverts commit 3ef5bdfeda.
2024-05-18 18:59:32 +02:00
Irmen de Jong
62afd3342e void syntax check, fixes #135 2024-05-18 17:15:31 +02:00
Irmen de Jong
6e8a89e6f1 optimize const word repeat setup 2024-05-18 16:30:27 +02:00
Irmen de Jong
aa2437cfb8 fix invalid repeat loop when iterations is already in register Y 2024-05-18 15:09:56 +02:00
Irmen de Jong
4a710ecdfc cleanups 2024-05-17 18:48:04 +02:00
Irmen de Jong
3ef5bdfeda update to Java 17 LTS 2024-05-17 18:27:21 +02:00
Irmen de Jong
7915dda35f update libraries 2024-05-12 03:02:54 +02:00
Irmen de Jong
9120e16683 todo 2024-05-02 21:02:50 +02:00
Irmen de Jong
a1ebc7090d fix sieve example 2024-04-18 22:22:29 +02:00
Irmen de Jong
054b4636e0 version 10.3 2024-04-18 21:50:48 +02:00
Irmen de Jong
e3e7b060b7 vumeter tweaks 2024-04-18 01:31:59 +02:00
Irmen de Jong
5ac9c75521 docs of new floats routines and added them to VM target too 2024-04-17 20:03:36 +02:00
markjreed
07710e0995
Feature/reciprocal tangent functions (#133)
* feat: additional trig functions

* fix: 64tass won't assemble a proc named 'sec'

* fix: indentation
2024-04-17 19:54:47 +02:00
Irmen de Jong
d6a67f5f2b vumeter colors 2024-04-17 00:22:19 +02:00
Irmen de Jong
2675623aea fix optimization ast parent linkage problem 2024-04-16 23:27:22 +02:00
Irmen de Jong
94263c43d0 added cx16/vumeter example 2024-04-16 22:48:36 +02:00
Irmen de Jong
d8ec03874f move the pi-related constants from system specific floats module into the shared one. Clarify some stuff. 2024-04-15 19:15:44 +02:00
Irmen de Jong
a7247f5b8b fix boolean expression optimization bug 2024-04-12 21:56:25 +02:00
Irmen de Jong
4d37581694 fix the symbol lookup error lsb(a) when a is in a multi vardecl. 2024-04-11 00:51:08 +02:00
Irmen de Jong
5d7ddebcad fix bool to uword cast in 6502 codegen 2024-04-11 00:34:53 +02:00
Irmen de Jong
53df0eb707 cleanups 2024-04-10 22:04:03 +02:00
Irmen de Jong
8babad9c7c sphinx config 2024-04-10 20:04:09 +02:00
Irmen de Jong
8db7aa07bd added (autogenerated) symbol skeleton files to the docs 2024-04-10 19:58:15 +02:00
Irmen de Jong
42f4b06ac8 added options -bytes2float and -float2bytes to be able to do float conversions from the command line 2024-04-09 23:59:54 +02:00
Irmen de Jong
f4b50368ba fix grammar: if_xx with else part 2024-04-09 22:35:30 +02:00
Irmen de Jong
db80417bd7 fix a problem with const fold optimization in if expressions, and IR compilation of that 2024-04-09 22:09:29 +02:00
Irmen de Jong
7a6f2ecc8c add symboldumps to doc makefile 2024-04-09 19:53:36 +02:00
Irmen de Jong
f5d556a7f9 added missing options to doc 2024-04-09 19:30:04 +02:00
Irmen de Jong
2aae46d632 added -dumpsymbols option to print a dump of all the variables and subroutine signatures 2024-04-09 19:19:13 +02:00
Irmen de Jong
19ebc6d6b3 better error message for ambiguous multi-var initialization in vardecl 2024-04-08 22:36:00 +02:00
Irmen de Jong
f88c29e083 convert github doc links into permalinks 2024-04-08 22:12:28 +02:00
Irmen de Jong
6ed9899dc7 smarter desugaring of ubyte x,y 2024-04-07 23:36:46 +02:00
Irmen de Jong
9de7698a5c verafx.mult() and muls() now return both words of the 32 bits result. 2024-04-07 22:41:21 +02:00
Irmen de Jong
112d2d6058 cx16 sprites module: the palette_offset parameter now takes values 0-15 (instead of 0-255) to be more consistent with docs and vera behavior 2024-04-07 21:49:03 +02:00
Irmen de Jong
ddb8346711 added txt.cls() as a shorter alternative to clear_screen().
cx16: added new character encodings, and routines in textio to enable the character sets for them.
cx16: added txt.chrout_lit() and txt.print_lit() to always print the literal characters and never as control codes
2024-04-07 19:32:44 +02:00
Irmen de Jong
8dd3faf395 clarification 2024-04-06 14:31:39 +02:00
Irmen de Jong
35f3e8708b doc and tweak subexpression extraction a tiny bit 2024-04-06 14:01:06 +02:00
Irmen de Jong
cfe3fcc9e7 fix symbol table issue 2024-04-06 12:53:33 +02:00
Irmen de Jong
66a6659a6e cbm.STOP2() and cbm.GETIN2() convenience routines 2024-04-06 02:16:21 +02:00
Irmen de Jong
88ae3daa42 Merge branch 'refs/heads/master' into multi-assign
# Conflicts:
#	examples/test.p8
2024-04-06 00:14:41 +02:00
Irmen de Jong
08b8fe01ab added missing cmp #0 after func()==0
cx16: diskio.fastmode() now returns success boolean
2024-04-06 00:04:54 +02:00
Irmen de Jong
731132d4b3 check number of result values in return statements 2024-04-05 02:13:31 +02:00
Irmen de Jong
98acff802f better checking for number of return values
assignment optimization if return register already is the same as the assignment target
2024-04-04 23:47:33 +02:00
Irmen de Jong
5f11f485a2 fix compiler error 2024-04-04 02:00:55 +02:00
Irmen de Jong
34f3169dda tweak library routines for multiple return values.
cbm:
MEMTOP changed (now also returns nr of banks in A)
STOP2 removed (just use STOP)
RDTIM_safe() added                  TEST IRQ ENABLE
RDTIM16 changed (internally)        TEST IRQ ENABLE

cx16:
screen_mode changed (now also returns width and height in X,Y)
kbdbuf_peek2 removed (just use kbdbuf_peek)
joystick_get changed (presence now returned as bool in Y)
joystick_get2 removed (just use joystick_get)
mouse_pos changed (now properly returns x and y position in R0 and R1)
set_led_brightness changed into set_led_state, with only a boolean on/off argument. There is no variable brightness.

sys.set_leds_brightness() removed. Use cx16.set_led_brightness().
2024-04-04 01:39:19 +02:00
Irmen de Jong
a3ef8f814b Merge branch 'master' into multi-assign
# Conflicts:
#	examples/test.p8
2024-04-03 01:13:27 +02:00
Irmen de Jong
385dd6fc23 todos 2024-04-03 01:12:45 +02:00
Irmen de Jong
9af4168ae2 cx16: added diskio.fastmode() to select the fast serial disk mode for the SD card 2024-04-02 22:17:51 +02:00
Irmen de Jong
a5e0e31b74 clarify order of multi-assign 2024-04-02 01:47:46 +02:00
Irmen de Jong
b385dc8c26 add cx16 extapi ROM call, call numbers and shims. (new in Rom R47) 2024-04-02 01:45:10 +02:00
Irmen de Jong
92c012b55a fix IR peephole optimization 2024-04-02 00:28:28 +02:00
Irmen de Jong
641f6c05d8 allow 'void' as dummy assign target in multi-assignment statements 2024-03-31 23:43:26 +02:00
Irmen de Jong
788f6b44a6 antlr grammar now understands underscores in identifier names 2024-03-31 00:31:10 +01:00
Irmen de Jong
63a4525f06 remove hacks from floats.parse now that kernal R47 is out 2024-03-30 22:29:13 +01:00
Irmen de Jong
3e34a3ef72 allow multi-assign to skip any status register result 2024-03-29 23:10:08 +01:00
Irmen de Jong
0c5e8ca199 Merge branch 'master' into multi-assign 2024-03-29 11:51:42 +01:00
Irmen de Jong
ff23fb0086 take ignore_unused option into account for warnings about removing unused blocks themselves as well 2024-03-29 00:16:18 +01:00
Irmen de Jong
56f41d5e34 docs about multi-assign 2024-03-28 23:24:14 +01:00
Irmen de Jong
4700a239b9 Merge branch 'master' into multi-assign
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2024-03-28 01:06:43 +01:00
Irmen de Jong
bd5abfb969 add IR peephole optimization to remove redundant store 2024-03-28 01:06:05 +01:00
Irmen de Jong
b93fa75377 consolidate cbm textio routines 2024-03-28 00:39:58 +01:00
Irmen de Jong
681ce9c60c fix void warning 2024-03-27 23:05:41 +01:00
Irmen de Jong
dd0f0fe415 conv.str_ub and partners are now much shorter routines than before 2024-03-27 22:34:44 +01:00
Irmen de Jong
119040fc50 also add diskio.status_code() in other comp targets 2024-03-27 20:05:39 +01:00
adiee5
551e5688da
Add diskio.status_code() function (#130) 2024-03-27 19:42:47 +01:00
Irmen de Jong
56c1035581 Merge branch 'master' into multi-assign
# Conflicts:
#	docs/source/todo.rst
#	examples/test.p8
2024-03-26 22:09:16 +01:00
Irmen de Jong
ba1e907c79 fix divmod; out args are written to and should be potential constants 2024-03-26 22:04:44 +01:00
Irmen de Jong
2a3a27c56d bmx library: set bpp header field correctly on save 2024-03-26 22:01:10 +01:00
markjreed
647af34f5b
fix: tweak divmod() doc (#131)
* fix: adjust naming on divmod parameters to match standard mathematical terminology; clarify description

* fix: wording

* fix: wording
2024-03-26 22:00:55 +01:00
Irmen de Jong
993be6394e unit tests multi-assigns 2024-03-25 23:20:03 +01:00
Irmen de Jong
9a27505315 6502 codegen for multi-assigns 2024-03-25 22:17:31 +01:00
Irmen de Jong
2e37f5dee3 IR: support for multi-returnvalue function calls (asmsubs)
note: the VM can't execute these though as it has no CPU hardware registers
2024-03-23 00:30:17 +01:00
Irmen de Jong
03e486c082 multi assign 2024-03-22 21:51:25 +01:00
Irmen de Jong
edc83305a4 allow multiple targets in AssignTarget 2024-03-22 21:51:08 +01:00
Irmen de Jong
66e7c51064 IR: fix some things related to asmsubs 2024-03-22 21:49:01 +01:00
Irmen de Jong
60244aaf16 64tass version... 2024-03-21 21:40:18 +01:00
Irmen de Jong
443391c700 another way to hash? 2024-03-21 21:30:34 +01:00
190 changed files with 22835 additions and 2316 deletions

View File

@ -18,34 +18,24 @@ jobs:
git clone --depth=1 https://github.com/irmen/64tass
cd 64tass
make -j4
sudo make install
sudo make install
- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: 11
distribution: adopt
distribution: temurin
- name: Build and test with Gradle
run: ./gradlew build shadowJar --no-daemon
run: |
./gradlew build shadowJar --no-daemon
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
- name: Create compiler shadowJar artifact
uses: actions/upload-artifact@v4
with:
name: prog8-compiler-jar-zipped
path: compiler/build/libs/*-all.jar
path: |
compiler/build/libs/*-all.jar
compiler/build/libs/hash.txt
- name: Calculate hash
uses: MCJack123/ghaction-generate-release-hashes@v4
if: github.event_name == 'release'
with:
get-assets: true
hash-type: sha256
file-name: hashes.txt
- name: Upload hashes
uses: actions/upload-artifact@v4
if: github.event_name == 'release'
with:
name: Artifact Hashes
path: hashes.txt

View File

@ -4,6 +4,6 @@
<option name="jvmTarget" value="11" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.20" />
<option name="version" value="1.9.24" />
</component>
</project>
</project>

View File

@ -1,23 +1,23 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.22" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.0/kotlin-stdlib-jdk8-2.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.0/kotlin-stdlib-2.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.0/kotlin-stdlib-jdk7-2.0.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.0/kotlin-stdlib-jdk8-2.0.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.0/kotlin-stdlib-2.0.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.0/kotlin-stdlib-jdk7-2.0.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.9.22/kotlin-stdlib-jdk8-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.0/kotlin-stdlib-jdk8-2.0.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.0/kotlin-stdlib-2.0.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.22/kotlin-stdlib-jdk7-1.9.22-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.0/kotlin-stdlib-jdk7-2.0.0-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -1,21 +1,18 @@
<component name="libraryTable">
<library name="io.kotest.assertions.core.jvm" type="repository">
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.8.0" />
<properties maven-id="io.kotest:kotest-assertions-core-jvm:5.9.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.0/kotest-assertions-core-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.0/kotest-assertions-shared-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.0/kotest-common-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.0/kotest-assertions-api-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

View File

@ -1,30 +1,30 @@
<component name="libraryTable">
<library name="io.kotest.runner.junit5.jvm" type="repository">
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.8.0" />
<properties maven-id="io.kotest:kotest-runner-junit5-jvm:5.9.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.8.0/kotest-runner-junit5-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.8.0/kotest-framework-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.8.0/kotest-assertions-shared-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-runner-junit5-jvm/5.9.0/kotest-runner-junit5-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-api-jvm/5.9.0/kotest-framework-api-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-shared-jvm/5.9.0/kotest-assertions-shared-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.7.0/kotlinx-coroutines-test-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.8.0/kotest-common-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.8.0/kotest-framework-engine-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.162/classgraph-4.8.162.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-test-jvm/1.8.0/kotlinx-coroutines-test-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-common-jvm/5.9.0/kotest-common-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-engine-jvm/5.9.0/kotest-framework-engine-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/github/classgraph/classgraph/4.8.172/classgraph-4.8.172.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/mordant/1.2.1/mordant-1.2.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/github/ajalt/colormath/1.2.0/colormath-1.2.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/opentest4j/opentest4j/1.3.0/opentest4j-1.3.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.7.0/kotlinx-coroutines-debug-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-debug/1.8.0/kotlinx-coroutines-debug-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna/5.9.0/jna-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/java/dev/jna/jna-platform/5.9.0/jna-platform-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy/1.10.9/byte-buddy-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/net/bytebuddy/byte-buddy-agent/1.10.9/byte-buddy-agent-1.10.9.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.8.0/kotest-framework-discovery-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.8.0/kotest-assertions-core-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.7.0/kotlinx-coroutines-jdk8-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.8.0/kotest-assertions-api-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.8.0/kotest-extensions-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.8.0/kotest-framework-concurrency-jvm-5.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.7.0/kotlinx-coroutines-core-jvm-1.7.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-discovery-jvm/5.9.0/kotest-framework-discovery-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-core-jvm/5.9.0/kotest-assertions-core-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-jdk8/1.8.0/kotlinx-coroutines-jdk8-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-assertions-api-jvm/5.9.0/kotest-assertions-api-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-extensions-jvm/5.9.0/kotest-extensions-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/io/kotest/kotest-framework-concurrency-jvm/5.9.0/kotest-framework-concurrency-jvm-5.9.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-coroutines-core-jvm/1.8.0/kotlinx-coroutines-core-jvm-1.8.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/23.0.0/annotations-23.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-engine/1.8.2/junit-platform-engine-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-commons/1.8.2/junit-platform-commons-1.8.2.jar!/" />
@ -32,11 +32,8 @@
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-suite-api/1.8.2/junit-platform-suite-api-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/platform/junit-platform-launcher/1.8.2/junit-platform-launcher-1.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/junit/jupiter/junit-jupiter-api/5.8.2/junit-jupiter-api-5.8.2.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.8.10/kotlin-stdlib-jdk8-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.8.10/kotlin-stdlib-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.8.10/kotlin-stdlib-jdk7-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-common/1.8.10/kotlin-stdlib-common-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.8.10/kotlin-reflect-1.8.10.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.23/kotlin-stdlib-1.9.23.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-reflect/1.9.23/kotlin-reflect-1.9.23.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />

View File

@ -1,8 +1,8 @@
<component name="libraryTable">
<library name="michael.bull.kotlin.result.jvm" type="repository">
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20" />
<properties maven-id="com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/1.1.20/kotlin-result-jvm-1.1.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/michael-bull/kotlin-result/kotlin-result-jvm/2.0.0/kotlin-result-jvm-2.0.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.9.22/kotlin-stdlib-1.9.22.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
</CLASSES>

View File

@ -22,7 +22,7 @@
<component name="FrameworkDetectionExcludesConfiguration">
<type id="Python" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="openjdk-11" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="openjdk-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@ -52,7 +52,7 @@ What does Prog8 provide?
------------------------
- all advantages of a higher level language over having to write assembly code manually
- programs run very fast because compilation to native machine code. It's possible to write games purely in Prog8, and even certain raster interrupt 'demoscene' effects.
- programs run very fast because compilation to native machine code
- modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings)
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
@ -71,7 +71,7 @@ What does Prog8 provide?
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- 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.
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
- encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16)
*Rapid edit-compile-run-debug cycle:*
@ -119,14 +119,13 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import textio
%zeropage basicsafe
main {
ubyte[256] sieve
bool[256] sieve
ubyte candidate_prime = 2 ; is increased in the loop
sub start() {
sys.memset(sieve, 256, false) ; clear the sieve
sys.memset(sieve, 256, 0) ; clear the sieve
txt.print("prime numbers up to 255:\n\n")
ubyte amount=0
repeat {
@ -162,9 +161,6 @@ 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)

View File

@ -6,9 +6,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@ -26,7 +25,7 @@ compileTestKotlin {
dependencies {
// should have no dependencies to other modules
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
}
sourceSets {

View File

@ -82,8 +82,7 @@ class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GL
override fun lookup(scopedName: String) = flat[scopedName]
fun getLength(name: String): Int? {
val node = flat[name]
return when(node) {
return when(val node = flat[name]) {
is StMemVar -> node.length
is StMemorySlab -> node.size.toInt()
is StStaticVariable -> node.length

View File

@ -44,7 +44,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
}
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
is PtIrRegister -> other is PtIrRegister && other.type==type && other.register==register
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
is PtNumber -> other is PtNumber && other.type==type && other.number==number
is PtBool -> other is PtBool && other.value==value
@ -87,7 +87,7 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
is PtContainmentCheck -> false
is PtFunctionCall -> false
is PtIdentifier -> true
is PtMachineRegister -> true
is PtIrRegister -> true
is PtMemoryByte -> address is PtNumber || address is PtIdentifier
is PtBool -> true
is PtNumber -> true
@ -139,13 +139,9 @@ class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(elementType, position) {
val variable: PtIdentifier
get() {
require((children[0] as? PtIdentifier)?.type in ArrayDatatypes+DataType.STR) // TODO remove
return children[0] as PtIdentifier
}
get() = children[0] as PtIdentifier
val index: PtExpression
get() = children[1] as PtExpression
val splitWords: Boolean
get() = variable.type in SplitWordArrayTypes
@ -210,11 +206,6 @@ class PtFunctionCall(val name: String,
val void: Boolean,
type: DataType,
position: Position) : PtExpression(type, position) {
init {
if(!void)
require(type!=DataType.UNDEFINED)
}
val args: List<PtExpression>
get() = children.map { it as PtExpression }
}
@ -239,7 +230,7 @@ class PtBool(val value: Boolean, position: Position) : PtExpression(DataType.BOO
companion object {
fun fromNumber(number: Number, position: Position): PtBool =
PtBool(if(number==0.0) false else true, position)
PtBool(number != 0.0, position)
}
override fun hashCode(): Int = Objects.hash(type, value)
@ -276,12 +267,12 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean {
if(other==null || other !is PtNumber)
return false
return if(other==null || other !is PtNumber)
false
else if(type!=DataType.BOOL && other.type!=DataType.BOOL)
return number==other.number
number==other.number
else
return type==other.type && number==other.number
type==other.type && number==other.number
}
operator fun compareTo(other: PtNumber): Int = number.compareTo(other.number)
@ -354,7 +345,7 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
class PtIrRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else if(expr is PtBool) expr.asInt().toDouble() else null

View File

@ -10,7 +10,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
fun txt(node: PtNode): String {
return when(node) {
is PtAssignTarget -> "<target>"
is PtAssignTarget -> if(node.void) "<void>" else "<target>"
is PtAssignment -> "<assign>"
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
is PtBreakpoint -> "%breakpoint"
@ -34,7 +34,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
str + node.name + "()"
}
is PtIdentifier -> "${node.name} ${type(node.type)}"
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
is PtIrRegister -> "IRREG#${node.register} ${type(node.type)}"
is PtMemoryByte -> "@()"
is PtNumber -> {
val numstr = if(node.type == DataType.FLOAT) node.number.toString() else node.number.toHex()
@ -63,18 +63,18 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
"???"
}
is PtAsmSub -> {
val params = node.parameters.map {
val register = it.first.registerOrPair
val statusflag = it.first.statusflag
"${it.second.type} ${it.second.name} @${register ?: statusflag}"
}.joinToString(", ")
val params = node.parameters.joinToString(", ") {
val register = it.first.registerOrPair
val statusflag = it.first.statusflag
"${it.second.type} ${it.second.name} @${register ?: statusflag}"
}
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
val returns = if (node.returns.isEmpty()) "" else {
"-> ${node.returns.map {
val register = it.first.registerOrPair
val statusflag = it.first.statusflag
"${it.second} @${register ?: statusflag}"}
.joinToString(", ")
"-> ${node.returns.joinToString(", ") {
val register = it.first.registerOrPair
val statusflag = it.first.statusflag
"${it.second} @${register ?: statusflag}"
}
}"
}
val str = if (node.inline) "inline " else ""
@ -108,7 +108,7 @@ fun printAst(root: PtNode, skipLibraries: Boolean, output: (text: String) -> Uni
}
}
is PtSub -> {
val params = node.parameters.map { "${it.type} ${it.name}" }.joinToString(", ")
val params = node.parameters.joinToString(", ") { "${it.type} ${it.name}" }
var str = "sub ${node.name}($params) "
if(node.returntype!=null)
str += "-> ${node.returntype.name.lowercase()}"

View File

@ -41,9 +41,18 @@ class PtSubroutineParameter(name: String, val type: DataType, position: Position
sealed interface IPtAssignment {
val children: MutableList<PtNode>
val target: PtAssignTarget
get() = children[0] as PtAssignTarget
get() {
if(children.size==2)
return children[0] as PtAssignTarget
else if(children.size<2)
throw AssemblyError("incomplete node")
else
throw AssemblyError("no singular target")
}
val value: PtExpression
get() = children[1] as PtExpression
get() = children.last() as PtExpression
val multiTarget: Boolean
get() = children.size>2
}
class PtAssignment(position: Position) : PtNode(position), IPtAssignment
@ -51,7 +60,7 @@ class PtAssignment(position: Position) : PtNode(position), IPtAssignment
class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position), IPtAssignment
class PtAssignTarget(position: Position) : PtNode(position) {
class PtAssignTarget(val void: Boolean, position: Position) : PtNode(position) {
val identifier: PtIdentifier?
get() = children.single() as? PtIdentifier
val array: PtArrayIndexer?
@ -69,7 +78,7 @@ class PtAssignTarget(position: Position) : PtNode(position) {
}
}
infix fun isSameAs(expression: PtExpression): Boolean = expression.isSameAs(this)
infix fun isSameAs(expression: PtExpression): Boolean = !void && expression.isSameAs(this)
}

View File

@ -96,9 +96,9 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"sqrt__ubyte" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UBYTE))), DataType.UBYTE),
"sqrt__uword" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
"sqrt__float" to FSignature(true, listOf(FParam("value", arrayOf(DataType.FLOAT))), DataType.FLOAT),
"divmod" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divident", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("division", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"divmod__ubyte" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UBYTE)), FParam("divident", arrayOf(DataType.UBYTE)), FParam("division", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
"divmod__uword" to FSignature(false, listOf(FParam("number", arrayOf(DataType.UWORD)), FParam("divident", arrayOf(DataType.UWORD)), FParam("division", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
"divmod" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("divisor", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("quotient", arrayOf(DataType.UBYTE, DataType.UWORD)), FParam("remainder", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"divmod__ubyte" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UBYTE)), FParam("divisor", arrayOf(DataType.UBYTE)), FParam("quotient", arrayOf(DataType.UBYTE)), FParam("remainder", arrayOf(DataType.UBYTE))), null),
"divmod__uword" to FSignature(false, listOf(FParam("dividend", arrayOf(DataType.UWORD)), FParam("divisor", arrayOf(DataType.UWORD)), FParam("quotient", arrayOf(DataType.UWORD)), FParam("remainder", arrayOf(DataType.UWORD))), null),
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.BOOL),
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
@ -133,4 +133,9 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"call" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
)
val InplaceModifyingBuiltinFunctions = setOf("setlsb", "setmsb", "rol", "ror", "rol2", "ror2", "sort", "reverse")
val InplaceModifyingBuiltinFunctions = setOf(
"setlsb", "setmsb",
"rol", "ror", "rol2", "ror2",
"sort", "reverse",
"divmod", "divmod__ubyte", "divmod__uword"
)

View File

@ -20,6 +20,7 @@ class CompilationOptions(val output: OutputType,
var asmListfile: Boolean = false,
var includeSourcelines: Boolean = false,
var dumpVariables: Boolean = false,
var dumpSymbols: Boolean = false,
var experimentalCodegen: Boolean = false,
var varsHighBank: Int? = null,
var varsGolden: Boolean = false,

View File

@ -78,7 +78,7 @@ enum class RegisterOrPair {
R8, R9, R10, R11, R12, R13, R14, R15;
companion object {
val names by lazy { values().map { it.toString()} }
val names by lazy { entries.map { it.toString()} }
fun fromCpuRegister(cpu: CpuRegister): RegisterOrPair {
return when(cpu) {
CpuRegister.A -> A
@ -104,7 +104,7 @@ enum class Statusflag {
Pn; // don't use
companion object {
val names by lazy { values().map { it.toString()} }
val names by lazy { entries.map { it.toString()} }
}
}

View File

@ -26,6 +26,9 @@ interface IMachineDefinition {
fun initializeMemoryAreas(compilerOptions: CompilationOptions)
fun getFloatAsmBytes(num: Number): String
fun convertFloatToBytes(num: Double): List<UByte>
fun convertBytesToFloat(bytes: List<UByte>): Double
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
fun launchEmulator(selectedEmulator: Int, programNameWithPath: Path)
fun isIOAddress(address: UInt): Boolean

View File

@ -5,7 +5,10 @@ enum class Encoding(val prefix: String) {
PETSCII("petscii"), // c64/c128/cx16
SCREENCODES("sc"), // c64/c128/cx16
ATASCII("atascii"), // atari
ISO("iso") // cx16
ISO("iso"), // cx16 (iso-8859-15)
ISO5("iso5"), // cx16 (iso-8859-5, cyrillic)
ISO16("iso16"), // cx16 (iso-8859-16, eastern european)
CP437("cp437") // cx16 (ibm pc, codepage 437)
}
interface IStringEncoding {

View File

@ -1,13 +1,18 @@
package prog8.code.optimize
import prog8.code.StRomSub
import prog8.code.SymbolTable
import prog8.code.ast.*
import prog8.code.core.*
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, errors: IErrorReporter) {
fun optimizeIntermediateAst(program: PtProgram, options: CompilationOptions, st: SymbolTable, errors: IErrorReporter) {
if (!options.optimize)
return
while(errors.noErrors() && optimizeCommonSubExpressions(program, errors)>0) {
while (errors.noErrors() &&
(optimizeCommonSubExpressions(program, errors)
+ optimizeAssignTargets(program, st, errors)) > 0
) {
// keep rolling
}
}
@ -27,11 +32,15 @@ private var tempVarCounter = 0
private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorReporter): Int {
fun extractableSubExpr(expr: PtExpression): Boolean {
if(expr is PtArrayIndexer && expr.index.isSimple())
return false
if (expr is PtMemoryByte && expr.address.isSimple())
return false
val result = if(expr is PtBinaryExpression)
expr.type !in ByteDatatypes ||
!expr.left.isSimple() ||
!expr.right.isSimple() ||
(expr.operator !in LogicalOperators && expr.operator !in BitwiseOperators)
!(expr.left.isSimple() && expr.right.isSimple()) ||
(expr.operator !in LogicalOperators && expr.operator !in BitwiseOperators)
else if (expr is PtArrayIndexer && expr.type !in ByteDatatypes)
true
else
@ -89,7 +98,7 @@ private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorRepor
singleReplacement2.parent = occurrence2.parent
val tempassign = PtAssignment(binexpr.position).also { assign ->
assign.add(PtAssignTarget(binexpr.position).also { tgt->
assign.add(PtAssignTarget(false, binexpr.position).also { tgt->
tgt.add(PtIdentifier("$containerScopedName.$tempvarName", datatype, binexpr.position))
})
assign.add(occurrence1)
@ -109,6 +118,79 @@ private fun optimizeCommonSubExpressions(program: PtProgram, errors: IErrorRepor
}
private fun optimizeAssignTargets(program: PtProgram, st: SymbolTable, errors: IErrorReporter): Int {
var changes = 0
walkAst(program) { node: PtNode, depth: Int ->
if(node is PtAssignment) {
val value = node.value
val functionName = when(value) {
is PtBuiltinFunctionCall -> value.name
is PtFunctionCall -> value.name
else -> null
}
if(functionName!=null) {
val stNode = st.lookup(functionName)
if (stNode is StRomSub) {
require(node.children.size==stNode.returns.size+1) {
"number of targets must match return values"
}
node.children.zip(stNode.returns).withIndex().forEach { (index, xx) ->
val target = xx.first as PtAssignTarget
val returnedRegister = xx.second.register.registerOrPair
if(returnedRegister!=null && !target.void && target.identifier!=null) {
if(isSame(target.identifier!!, xx.second.type, returnedRegister)) {
// output register is already identical to target register, so it can become void
val voidTarget = PtAssignTarget(true, target.position)
node.children[index] = voidTarget
voidTarget.parent = node
changes++
}
}
}
}
if(node.children.dropLast(1).all { (it as PtAssignTarget).void }) {
// all targets are now void, the whole assignment can be discarded and replaced by just a (void) call to the subroutine
val index = node.parent.children.indexOf(node)
val voidCall = PtFunctionCall(functionName, true, value.type, value.position)
value.children.forEach { voidCall.add(it) }
node.parent.children[index] = voidCall
voidCall.parent = node.parent
changes++
}
}
}
true
}
return changes
}
internal fun isSame(identifier: PtIdentifier, type: DataType, returnedRegister: RegisterOrPair): Boolean {
if(returnedRegister in Cx16VirtualRegisters) {
val regname = returnedRegister.name.lowercase()
val identifierRegName = identifier.name.substringAfterLast('.')
/*
cx16.r? UWORD
cx16.r?s WORD
cx16.r?L UBYTE
cx16.r?H UBYTE
cx16.r?sL BYTE
cx16.r?sH BYTE
*/
if(identifier.type in ByteDatatypes && type in ByteDatatypes) {
if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) {
return identifierRegName.substring(2) in arrayOf("", "L", "sL") // note: not the -H (msb) variants!
}
}
else if(identifier.type in WordDatatypes && type in WordDatatypes) {
if(identifier.name.startsWith("cx16.$regname") && identifierRegName.startsWith(regname)) {
return identifierRegName.substring(2) in arrayOf("", "s")
}
}
}
return false // there are no identifiers directly corresponding to cpu registers
}
internal fun findScopeName(node: PtNode): String {
var parent=node
while(parent !is PtNamedNode)

View File

@ -19,3 +19,23 @@ class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by Cb
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
}
}
val CompilationTargets = listOf(
C64Target.NAME,
C128Target.NAME,
Cx16Target.NAME,
PETTarget.NAME,
AtariTarget.NAME,
VMTarget.NAME
)
fun getCompilationTargetByName(name: String) = when(name.lowercase()) {
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
Cx16Target.NAME -> Cx16Target()
PETTarget.NAME -> PETTarget()
AtariTarget.NAME -> AtariTarget()
VMTarget.NAME -> VMTarget()
else -> throw IllegalArgumentException("invalid compilation target")
}

View File

@ -4,9 +4,7 @@ import com.github.michaelbull.result.fold
import prog8.code.core.Encoding
import prog8.code.core.IStringEncoding
import prog8.code.core.InternalCompilerException
import prog8.code.target.cbm.AtasciiEncoding
import prog8.code.target.cbm.IsoEncoding
import prog8.code.target.cbm.PetsciiEncoding
import prog8.code.target.encodings.*
object Encoder: IStringEncoding {
@ -18,6 +16,9 @@ object Encoder: IStringEncoding {
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
Encoding.ISO -> IsoEncoding.encode(str)
Encoding.ATASCII -> AtasciiEncoding.encode(str)
Encoding.ISO5 -> IsoCyrillicEncoding.encode(str)
Encoding.ISO16 -> IsoEasternEncoding.encode(str)
Encoding.CP437 -> Cp437Encoding.encode(str)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
return coded.fold(
@ -31,6 +32,9 @@ object Encoder: IStringEncoding {
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
Encoding.ISO -> IsoEncoding.decode(bytes)
Encoding.ATASCII -> AtasciiEncoding.decode(bytes)
Encoding.ISO5 -> IsoCyrillicEncoding.decode(bytes)
Encoding.ISO16 -> IsoEasternEncoding.decode(bytes)
Encoding.CP437 -> Cp437Encoding.decode(bytes)
else -> throw InternalCompilerException("unsupported encoding $encoding")
}
return decoded.fold(

View File

@ -22,6 +22,8 @@ class AtariMachineDefinition: IMachineDefinition {
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = TODO("atari float asm bytes from number")
override fun convertFloatToBytes(num: Double): List<UByte> = TODO("atari float to bytes")
override fun convertBytesToFloat(bytes: List<UByte>): Double = TODO("atari bytes to float")
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.output == OutputType.XEX)

View File

@ -25,6 +25,17 @@ class C128MachineDefinition: IMachineDefinition {
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")

View File

@ -26,6 +26,17 @@ class C64MachineDefinition: IMachineDefinition {
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")

View File

@ -24,6 +24,18 @@ class CX16MachineDefinition: IMachineDefinition {
override lateinit var golden: GoldenRam
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")

View File

@ -1,4 +1,4 @@
package prog8.code.target.cbm
package prog8.code.target.encodings
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result

View File

@ -0,0 +1,69 @@
package prog8.code.target.encodings
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
object Cp437Encoding {
val charset: Charset = Charset.forName("IBM437")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
return try {
val mapped = str.map { chr ->
when (chr) {
'\u0000' -> 0u
'\u00a0' -> 255u
'☺' -> 1u
'☻' -> 2u
'♥' -> 3u
'♦' -> 4u
'♣' -> 5u
'♠' -> 6u
'•' -> 7u
'◘' -> 8u
'○' -> 9u
'◙' -> 10u
'♂' -> 11u
'♀' -> 12u
'♪' -> 13u
'♫' -> 14u
'☼' -> 15u
'►' -> 16u
'◄' -> 17u
'↕' -> 18u
'‼' -> 19u
'¶' -> 20u
'§' -> 21u
'▬' -> 22u
'↨' -> 23u
'↑' -> 24u
'↓' -> 25u
'→' -> 26u
'←' -> 27u
'∟' -> 28u
'↔' -> 29u
'▲' -> 30u
'▼' -> 31u
in '\u8000'..'\u80ff' -> {
// special case: take the lower 8 bit hex value directly
(chr.code - 0x8000).toUByte()
}
else -> charset.encode(chr.toString())[0].toUByte()
}
}
Ok(mapped)
} catch (ce: CharConversionException) {
Err(ce)
}
}
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}
}
}

View File

@ -1,4 +1,4 @@
package prog8.code.target.cbm
package prog8.code.target.encodings
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
@ -6,8 +6,8 @@ import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
object IsoEncoding {
val charset: Charset = Charset.forName("ISO-8859-15")
open class IsoEncodingBase(charsetName: String) {
val charset: Charset = Charset.forName(charsetName)
fun encode(str: String): Result<List<UByte>, CharConversionException> {
return try {
@ -35,3 +35,8 @@ object IsoEncoding {
}
}
}
object IsoEncoding: IsoEncodingBase("ISO-8859-15")
object IsoCyrillicEncoding: IsoEncodingBase("ISO-8859-5")
object IsoEasternEncoding: IsoEncodingBase("ISO-8859-16")

View File

@ -1,4 +1,4 @@
package prog8.code.target.cbm
package prog8.code.target.encodings
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok

View File

@ -25,6 +25,17 @@ class PETMachineDefinition: IMachineDefinition {
override fun getFloatAsmBytes(num: Number) = Mflpt5.fromNumber(num).makeFloatFillAsm()
override fun convertFloatToBytes(num: Double): List<UByte> {
val m5 = Mflpt5.fromNumber(num)
return listOf(m5.b0, m5.b1, m5.b2, m5.b3, m5.b4)
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==5) { "need 5 bytes" }
val m5 = Mflpt5(bytes[0], bytes[1], bytes[2], bytes[3], bytes[4])
return m5.toDouble()
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == CbmPrgLauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")

View File

@ -30,6 +30,26 @@ class VirtualMachineDefinition: IMachineDefinition {
return parts.joinToString(", ")
}
override fun convertFloatToBytes(num: Double): List<UByte> {
val bits = num.toBits().toULong()
val hexStr = bits.toString(16).padStart(16, '0')
val parts = hexStr.chunked(2).map { it.toInt(16).toUByte() }
return parts
}
override fun convertBytesToFloat(bytes: List<UByte>): Double {
require(bytes.size==8) { "need 8 bytes" }
val b0 = bytes[0].toLong() shl (8*7)
val b1 = bytes[1].toLong() shl (8*6)
val b2 = bytes[2].toLong() shl (8*5)
val b3 = bytes[3].toLong() shl (8*4)
val b4 = bytes[4].toLong() shl (8*3)
val b5 = bytes[5].toLong() shl (8*2)
val b6 = bytes[6].toLong() shl (8*1)
val b7 = bytes[7].toLong() shl (8*0)
return Double.fromBits(b0 or b1 or b2 or b3 or b4 or b5 or b6 or b7)
}
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return listOf("syslib")
}

View File

@ -5,9 +5,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@ -26,9 +25,9 @@ dependencies {
implementation project(':codeCore')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

View File

@ -66,7 +66,7 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
if(node.type in SplitWordArrayTypes && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
lookupName = lookupName.dropLast(4)
}
val stNode = st.lookup(lookupName)!!
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
if(stNode.astNode.definingBlock()?.options?.noSymbolPrefixing!=true) {
val index = node.parent.children.indexOf(node)
nodesToPrefix += node.parent to index
@ -100,8 +100,7 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
}
nodesToPrefix.forEach { (parent, index) ->
val node = parent.children[index]
when(node) {
when(val node = parent.children[index]) {
is PtIdentifier -> parent.children[index] = node.prefix(parent, st)
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
is PtJump -> parent.children[index] = node.prefix(parent, st)
@ -247,7 +246,7 @@ class AsmGen6502Internal (
if(errors.noErrors()) {
val output = options.outputDir.resolve("${program.name}.asm")
val asmLines = assembly.asSequence().flatMapTo(mutableListOf()) { it.split('\n') }
val asmLines = assembly.flatMapTo(mutableListOf()) { it.split('\n') }
if(options.compTarget.name==Cx16Target.NAME) {
scanInvalid65816instructions(asmLines)
if(!errors.noErrors()) {
@ -587,7 +586,10 @@ class AsmGen6502Internal (
is PtInlineAssembly -> translate(stmt)
is PtBuiltinFunctionCall -> builtinFunctionsAsmGen.translateFunctioncallStatement(stmt)
is PtFunctionCall -> functioncallAsmGen.translateFunctionCallStatement(stmt)
is PtAssignment -> assignmentAsmGen.translate(stmt)
is PtAssignment -> {
if(stmt.multiTarget) assignmentAsmGen.translateMultiAssign(stmt)
else assignmentAsmGen.translate(stmt)
}
is PtAugmentedAssign -> assignmentAsmGen.translate(stmt)
is PtJump -> {
val (asmLabel, indirect) = getJumpTarget(stmt)
@ -817,17 +819,15 @@ class AsmGen6502Internal (
loopEndLabels.pop()
}
private fun repeatWordCount(count: Int, stmt: PtRepeatLoop) {
require(count in 257..65535) { "invalid repeat count ${stmt.position}" }
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
require(iterations in 257..65535) { "invalid repeat count ${stmt.position}" }
val repeatLabel = makeLabel("repeat")
val counterVar = createRepeatCounterVar(DataType.UWORD, isTargetCpu(CpuType.CPU65c02), stmt)
// the iny + double dec is microoptimization of the 16 bit loop
val loopcount = if(iterations and 0x00ff == 0) iterations else iterations + 0x0100 // so that the loop can simply use a double-dec
out("""
ldy #>$count
lda #<$count
beq +
iny
+ sta $counterVar
ldy #>$loopcount
lda #<$loopcount
sta $counterVar
sty $counterVar+1
$repeatLabel""")
translate(stmt.statements)
@ -880,8 +880,8 @@ $repeatLabel""")
}
private fun repeatCountInY(stmt: PtRepeatLoop, endLabel: String) {
// note: Y must just have been loaded with the (variable) number of loops to be performed!
val repeatLabel = makeLabel("repeat")
out(" cpy #0")
if(isTargetCpu(CpuType.CPU65c02)) {
val counterVar = createRepeatCounterVar(DataType.UBYTE, true, stmt)
out(" beq $endLabel | sty $counterVar")
@ -1317,91 +1317,6 @@ $repeatLabel""")
}
}
internal fun popCpuStack(asmsub: PtAsmSub, parameter: PtSubroutineParameter, reg: RegisterOrStatusflag) {
val shouldKeepA = asmsub.parameters.any { it.first.registerOrPair==RegisterOrPair.AX || it.first.registerOrPair==RegisterOrPair.AY}
if(reg.statusflag!=null) {
if(shouldKeepA)
out(" sta P8ZP_SCRATCH_REG")
out("""
clc
pla
beq +
sec
+""")
if(shouldKeepA)
out(" lda P8ZP_SCRATCH_REG")
}
else {
if (parameter.type in ByteDatatypesWithBoolean) {
if (isTargetCpu(CpuType.CPU65c02)) {
when (reg.registerOrPair) {
RegisterOrPair.A -> out(" pla")
RegisterOrPair.X -> out(" plx")
RegisterOrPair.Y -> out(" ply")
in Cx16VirtualRegisters -> out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
} else {
when (reg.registerOrPair) {
RegisterOrPair.A -> out(" pla")
RegisterOrPair.X -> {
if(shouldKeepA)
out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
else
out(" pla | tax")
}
RegisterOrPair.Y -> {
if(shouldKeepA)
out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
else
out(" pla | tay")
}
in Cx16VirtualRegisters -> out(" pla | sta cx16.${reg.registerOrPair!!.name.lowercase()}")
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
} else {
// word pop
if (isTargetCpu(CpuType.CPU65c02))
when (reg.registerOrPair) {
RegisterOrPair.AX -> out(" plx | pla")
RegisterOrPair.AY -> out(" ply | pla")
RegisterOrPair.XY -> out(" ply | plx")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
else {
when (reg.registerOrPair) {
RegisterOrPair.AX -> out(" pla | tax | pla")
RegisterOrPair.AY -> out(" pla | tay | pla")
RegisterOrPair.XY -> out(" pla | tay | pla | tax")
in Cx16VirtualRegisters -> {
val regname = reg.registerOrPair!!.name.lowercase()
out(" pla | sta cx16.$regname+1 | pla | sta cx16.$regname")
}
else -> throw AssemblyError("invalid target register ${reg.registerOrPair}")
}
}
}
}
}
internal fun popCpuStack(dt: DataType) {
if (dt in ByteDatatypesWithBoolean) {
out(" pla")
} else if (dt in WordDatatypes) {
if (isTargetCpu(CpuType.CPU65c02))
out(" ply | pla")
else
out(" pla | tay | pla")
} else {
throw AssemblyError("can't pop type $dt")
}
}
internal fun pushCpuStack(dt: DataType, value: PtExpression) {
val signed = value.type.oneOf(DataType.BYTE, DataType.WORD)
if(dt in ByteDatatypesWithBoolean) {

View File

@ -65,8 +65,6 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
numberOfOptimizations++
}
// TODO more assembly peephole optimizations
return numberOfOptimizations
}

View File

@ -28,7 +28,7 @@ internal class AssemblyProgram(
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", // "-Wno-strict-bool", "-Werror",
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile", "--no-monitor"
"--dump-labels", "--vice-labels", "--labels=$viceMonListFile"
)
if(options.warnSymbolShadowing)
@ -39,8 +39,9 @@ internal class AssemblyProgram(
if(options.asmQuiet)
command.add("--quiet")
if(options.asmListfile)
command.add("--list=$listFile")
if(options.asmListfile) {
command.addAll(listOf("--list=$listFile", "--no-monitor"))
}
val outFile = when (options.output) {
OutputType.PRG -> {

View File

@ -82,8 +82,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
val source = fcall.args[0] as PtIdentifier
val target = fcall.args[1] as PtIdentifier
val sourceSymbol = asmgen.symbolTable.lookup(source.name)
val numElements = when(sourceSymbol) {
val numElements = when(val sourceSymbol = asmgen.symbolTable.lookup(source.name)) {
is StStaticVariable -> sourceSymbol.length!!
is StMemVar -> sourceSymbol.length!!
else -> 0
@ -827,8 +826,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun funcSgn(fcall: PtBuiltinFunctionCall, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
translateArguments(fcall, scope)
val dt = fcall.args.single().type
when (dt) {
when (val dt = fcall.args.single().type) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
@ -1398,8 +1396,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
private fun outputAddressAndLengthOfArray(arg: PtIdentifier) {
// address goes in P8ZP_SCRATCH_W1, number of elements in A
val symbol = asmgen.symbolTable.lookup(arg.name)
val numElements = when(symbol) {
val numElements = when(val symbol = asmgen.symbolTable.lookup(arg.name)) {
is StStaticVariable -> symbol.length!!
is StMemVar -> symbol.length!!
else -> 0

View File

@ -335,8 +335,7 @@ $endLabel""")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val symbol = asmgen.symbolTable.lookup(ident.name)
val numElements = when(symbol) {
val numElements = when(val symbol = asmgen.symbolTable.lookup(ident.name)) {
is StStaticVariable -> symbol.length!!
is StMemVar -> symbol.length!!
else -> 0

View File

@ -81,7 +81,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
is PtAddressOf -> false
is PtIdentifier -> false
is PtMachineRegister -> false
is PtIrRegister -> false
is PtMemoryByte -> return usesOtherRegistersWhileEvaluating(arg.address)
is PtNumber -> false
is PtBool -> false
@ -90,7 +90,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
}
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
val registersUsed = mutableListOf<RegisterOrStatusflag>();
val registersUsed = mutableListOf<RegisterOrStatusflag>()
fun usedA() = registersUsed.any {it.registerOrPair==RegisterOrPair.A || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.AY}
fun usedX() = registersUsed.any {it.registerOrPair==RegisterOrPair.X || it.registerOrPair==RegisterOrPair.AX || it.registerOrPair==RegisterOrPair.XY}

View File

@ -70,7 +70,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
is PtNumber,
is PtBool,
is PtIdentifier,
is PtMachineRegister,
is PtIrRegister,
is PtArrayIndexer,
is PtPrefix,
is PtBinaryExpression -> { /* no cmp necessary the lda has been done just prior */ }
@ -193,7 +193,7 @@ internal class IfElseAsmGen(private val program: PtProgram,
else
translateIfElseBodies("beq", stmt)
} else {
errors.warn("SLOW FALLBACK FOR 'IF' CODEGEN - ask for support", stmt.position) // TODO should have no more of these at all
errors.warn("SLOW FALLBACK FOR 'IF' CODEGEN - ask for support", stmt.position) // should not occur ;-)
assignConditionValueToRegisterAndTest(stmt.condition)
if(jumpAfterIf!=null)
translateJumpElseBodies("bne", "beq", jumpAfterIf, stmt.elseScope)
@ -405,14 +405,14 @@ internal class IfElseAsmGen(private val program: PtProgram,
val condition = stmt.condition as PtBinaryExpression
if(signed) {
// X>Y --> Y<X
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, signed)
asmgen.assignExpressionToRegister(condition.right, RegisterOrPair.A, true)
cmpAwithByteValue(condition.left, true)
if (jumpAfterIf != null)
translateJumpElseBodies("bmi", "bpl", jumpAfterIf, stmt.elseScope)
else
translateIfElseBodies("bpl", stmt)
} else {
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, signed)
asmgen.assignExpressionToRegister(condition.left, RegisterOrPair.A, false)
cmpAwithByteValue(condition.right, false)
if(jumpAfterIf!=null) {
val (asmLabel, indirect) = asmgen.getJumpTarget(jumpAfterIf)
@ -616,7 +616,7 @@ _jump jmp ($asmLabel)
if(right is PtIdentifier) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY, signed)
val variable = asmgen.asmVariableName(right)
return code(variable, variable+"+1")
return code(variable, "$variable+1")
}
// TODO optimize for simple array value
@ -737,7 +737,7 @@ _jump jmp ($asmLabel)
if(right is PtIdentifier) {
asmgen.assignExpressionToRegister(left, RegisterOrPair.AY, signed)
val variable = asmgen.asmVariableName(right)
return code(variable, variable+"+1")
return code(variable, "$variable+1")
}
// TODO optimize for simple array value
@ -848,10 +848,10 @@ _jump jmp ($asmLabel)
}
} else {
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
return if(value.splitWords) {
compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
} else {
return compareLsbMsb("$varname,y", "$varname+1,y")
compareLsbMsb("$varname,y", "$varname+1,y")
}
}
}
@ -985,10 +985,10 @@ _jump jmp ($asmLabel)
}
} else {
asmgen.loadScaledArrayIndexIntoRegister(value, CpuRegister.Y)
if(value.splitWords) {
return compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
return if(value.splitWords) {
compareLsbMsb("${varname}_lsb,y", "${varname}_msb,y")
} else {
return compareLsbMsb("$varname,y", "$varname+1,y")
compareLsbMsb("$varname,y", "$varname+1,y")
}
}
}
@ -1514,7 +1514,7 @@ _jump jmp ($asmLabel)
else -> {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varname = asmgen.asmVariableName(left)
translateAYNotEquals(varname, varname + "+1")
translateAYNotEquals(varname, "$varname+1")
}
}
}
@ -1562,7 +1562,7 @@ _jump jmp ($asmLabel)
else -> {
asmgen.assignExpressionToRegister(right, RegisterOrPair.AY, signed)
val varname = asmgen.asmVariableName(left)
translateAYEquals(varname, varname+"+1")
translateAYEquals(varname, "$varname+1")
}
}
}

View File

@ -635,8 +635,7 @@ internal class ProgramAndVarsGen(
} else 0
when (variable.dt) {
DataType.BOOL -> TODO("bool var to asm")
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
DataType.BOOL, DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
DataType.BYTE -> asmgen.out("${variable.name}\t.char $initialValue")
DataType.UWORD -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
DataType.WORD -> asmgen.out("${variable.name}\t.sint $initialValue")

View File

@ -48,9 +48,9 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
val numberOfAllocatableVariables = allVariables.size
val varsRequiringZp = allVariables.filter { it.zpwish == ZeropageWish.REQUIRE_ZEROPAGE }
val varsPreferringZp = allVariables.filter { it.zpwish == ZeropageWish.PREFER_ZEROPAGE }
val varsNotZp = allVariables.filter { it.zpwish == ZeropageWish.NOT_IN_ZEROPAGE }
val varsDontCare = allVariables.filter { it.zpwish == ZeropageWish.DONTCARE }
val numberOfExplicitNonZpVariables = allVariables.count { it.zpwish == ZeropageWish.NOT_IN_ZEROPAGE }
require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + numberOfExplicitNonZpVariables == numberOfAllocatableVariables)
require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + varsNotZp.size == numberOfAllocatableVariables)
var numVariablesAllocatedInZP = 0
var numberOfNonIntegerVariables = 0
@ -86,7 +86,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
// no need to check for allocation error, if there is one, just allocate in normal system ram.
}
// try to allocate any other interger variables into the zeropage until it is full.
// try to allocate the "don't care" interger variables into the zeropage until it is full.
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
if(errors.noErrors()) {
val sortedList = varsDontCare.sortedByDescending { it.scopedName }
@ -110,9 +110,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
}
}
// println(" number of allocated vars: $numberOfAllocatableVariables")
// println(" put into zeropage: $numVariablesAllocatedInZP, non-zp allocatable: ${numberOfNonIntegerVariables+numberOfExplicitNonZpVariables}")
// println(" zeropage free space: ${zeropage.free.size} bytes")
// note: no zeropage allocation is done at all for the @nozp variables. This means they will always end up outside the zeropage.
}
private fun collectAllVariables(st: SymbolTable): Collection<StStaticVariable> {

View File

@ -35,7 +35,7 @@ internal class AnyExprAsmGen(
require(expr.left.type in WordDatatypes && expr.right.type in WordDatatypes) {
"both operands must be words"
}
return assignWordBinExpr(expr)
throw AssemblyError("expression should have been handled otherwise: word ${expr.operator} at ${expr.position}")
}
DataType.FLOAT -> {
require(expr.left.type==DataType.FLOAT && expr.right.type==DataType.FLOAT) {
@ -47,30 +47,6 @@ internal class AnyExprAsmGen(
}
}
private fun assignWordBinExpr(expr: PtBinaryExpression): Boolean {
when(expr.operator) {
"+" -> TODO("word + at ${expr.position}")
"-" -> TODO("word - at ${expr.position}")
"*" -> TODO("word * at ${expr.position}")
"/" -> TODO("word / at ${expr.position}")
"<<" -> TODO("word << at ${expr.position}")
">>" -> TODO("word >> at ${expr.position}")
"%" -> TODO("word % at ${expr.position}")
"and" -> TODO("word logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("word logical or (with optional shortcircuit) ${expr.position}")
"&" -> TODO("word and at ${expr.position}")
"|" -> TODO("word or at ${expr.position}")
"^", "xor" -> TODO("word xor at ${expr.position}")
"==" -> TODO("word == at ${expr.position}")
"!=" -> TODO("word != at ${expr.position}")
"<" -> TODO("word < at ${expr.position}")
"<=" -> TODO("word <= at ${expr.position}")
">" -> TODO("word > at ${expr.position}")
">=" -> TODO("word >= at ${expr.position}")
else -> return false
}
}
private fun assignByteBinExpr(expr: PtBinaryExpression, assign: AsmAssignment): Boolean {
when(expr.operator) {
"+" -> {
@ -89,21 +65,11 @@ internal class AnyExprAsmGen(
asmgen.assignRegister(RegisterOrPair.A, assign.target)
return true
}
"*" -> {
TODO("byte * at ${expr.position}")
}
"/" -> {
TODO("byte / at ${expr.position}")
}
"<<" -> {
TODO("byte << at ${expr.position}")
}
">>" -> {
TODO("byte >> at ${expr.position}")
}
"%" -> {
TODO("byte % at ${expr.position}")
}
"*" -> TODO("byte * at ${expr.position}")
"/" -> TODO("byte / at ${expr.position}")
"<<" -> TODO("byte << at ${expr.position}")
">>" -> TODO("byte >> at ${expr.position}")
"%" -> TODO("byte % at ${expr.position}")
"and" -> TODO("logical and (with optional shortcircuit) ${expr.position}")
"or" -> TODO("logical or (with optional shortcircuit) ${expr.position}")
"&" -> {
@ -130,24 +96,12 @@ internal class AnyExprAsmGen(
asmgen.assignRegister(RegisterOrPair.A, assign.target)
return true
}
"==" -> {
TODO("byte == at ${expr.position}")
}
"!=" -> {
TODO("byte != at ${expr.position}")
}
"<" -> {
TODO("byte < at ${expr.position}")
}
"<=" -> {
TODO("byte <= at ${expr.position}")
}
">" -> {
TODO("byte > at ${expr.position}")
}
">=" -> {
TODO("byte >= at ${expr.position}")
}
"==" -> TODO("byte == at ${expr.position}")
"!=" -> TODO("byte != at ${expr.position}")
"<" -> TODO("byte < at ${expr.position}")
"<=" -> TODO("byte <= at ${expr.position}")
">" -> TODO("byte > at ${expr.position}")
">=" -> TODO("byte >= at ${expr.position}")
else -> return false
}
}

View File

@ -1,6 +1,8 @@
package prog8.codegen.cpu6502.assignment
import prog8.code.StMemVar
import prog8.code.StRomSub
import prog8.code.StRomSubParameter
import prog8.code.StStaticVariable
import prog8.code.ast.*
import prog8.code.core.*
@ -32,6 +34,145 @@ internal class AssignmentAsmGen(private val program: PtProgram,
augmentableAsmGen.translate(assign, augmentedAssign.definingISub())
}
fun translateMultiAssign(assignment: PtAssignment) {
val values = assignment.value as? PtFunctionCall
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
val sub = asmgen.symbolTable.lookup(values.name) as? StRomSub
?: throw AssemblyError("only asmsubs can return multiple values")
require(sub.returns.size>=2)
if(sub.returns.any { it.type==DataType.FLOAT })
TODO("deal with (multiple?) FP return registers")
asmgen.translate(values)
fun needsToSaveA(registersResults: List<Pair<StRomSubParameter, PtNode>>): Boolean =
if(registersResults.isEmpty())
false
else if(registersResults.all { (it.second as PtAssignTarget).identifier!=null})
false
else
true
fun assignCarryFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out(" lda #0 | rol a")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
}
fun assignZeroFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out("""
beq +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
}
fun assignNegativeFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out("""
bmi +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
}
fun assignOverflowFlagResult(target: PtAssignTarget, saveA: Boolean) {
if(saveA) asmgen.out(" pha")
asmgen.out("""
bvs +
lda #0
beq ++
+ lda #1
+""")
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
assignRegisterByte(tgt, CpuRegister.A, false, false)
if(saveA) asmgen.out(" pla")
}
fun assignRegisterResults(registersResults: List<Pair<StRomSubParameter, PtNode>>) {
registersResults.forEach { (returns, target) ->
target as PtAssignTarget
if(!target.void) {
val targetIdent = target.identifier
val targetMem = target.memory
if(targetIdent!=null || targetMem!=null) {
val tgt = AsmAssignTarget.fromAstAssignment(target, target.definingISub(), asmgen)
when(returns.type) {
in ByteDatatypesWithBoolean -> {
if(returns.register.registerOrPair in Cx16VirtualRegisters) {
assignVirtualRegister(tgt, returns.register.registerOrPair!!)
} else {
assignRegisterByte(tgt, returns.register.registerOrPair!!.asCpuRegister(), false, false)
}
}
in WordDatatypes -> {
assignRegisterpairWord(tgt, returns.register.registerOrPair!!)
}
else -> throw AssemblyError("weird dt")
}
}
else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity
}
}
}
val assignmentTargets = assignment.children.dropLast(1)
if(sub.returns.size==assignmentTargets.size) {
// because we can only handle integer results right now we can just zip() it all up
val (statusFlagResults, registersResults) = sub.returns.zip(assignmentTargets).partition { it.first.register.statusflag!=null }
if(statusFlagResults.isNotEmpty()) {
if(statusFlagResults.size>1)
TODO("handle multiple status flag results")
val (returns, target) = statusFlagResults.single()
target as PtAssignTarget
if(target.void) {
// forget about the Carry status flag, only assign the normal return values
assignRegisterResults(registersResults)
return
}
if(registersResults.all {
val tgt = it.second as PtAssignTarget
tgt.void || tgt.identifier!=null})
{
// all other results are just stored into identifiers directly so first handle those
// (simple store instructions that don't modify the carry flag)
assignRegisterResults(registersResults)
return when(returns.register.statusflag!!) {
Statusflag.Pc -> assignCarryFlagResult(target, false)
Statusflag.Pz -> assignZeroFlagResult(target, false)
Statusflag.Pv -> assignOverflowFlagResult(target, false)
Statusflag.Pn -> assignNegativeFlagResult(target, false)
}
}
val saveA = needsToSaveA(registersResults)
when(returns.register.statusflag!!) {
Statusflag.Pc -> assignCarryFlagResult(target, saveA)
Statusflag.Pz -> assignZeroFlagResult(target, saveA)
Statusflag.Pv -> assignOverflowFlagResult(target, saveA)
Statusflag.Pn -> assignNegativeFlagResult(target, saveA)
}
}
assignRegisterResults(registersResults)
} else {
throw AssemblyError("number of values and targets don't match")
}
}
fun translateNormalAssignment(assign: AsmAssignment, scope: IPtSubroutine?) {
when(assign.source.kind) {
SourceStorageKind.LITERALBOOLEAN -> {
@ -597,7 +738,6 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignTrue.add(target)
assignTrue.add(PtNumber.fromBoolean(true, assign.position))
} else {
require(assign.target.datatype in ByteDatatypes)
when(assign.target.kind) {
TargetStorageKind.VARIABLE -> {
if(assign.target.datatype in WordDatatypes) {
@ -618,7 +758,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
}
}
TargetStorageKind.MEMORY -> {
val tgt = PtAssignTarget(assign.target.position)
val tgt = PtAssignTarget(false, assign.target.position)
val targetmem = assign.target.memory!!
val mem = PtMemoryByte(targetmem.position)
mem.add(targetmem.address)
@ -628,7 +768,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
assignTrue.add(PtNumber.fromBoolean(true, assign.position))
}
TargetStorageKind.ARRAY -> {
val tgt = PtAssignTarget(assign.target.position)
val tgt = PtAssignTarget(false, assign.target.position)
val targetarray = assign.target.array!!
val array = PtArrayIndexer(assign.target.datatype, targetarray.position)
array.add(targetarray.variable)
@ -667,7 +807,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
internal fun directIntoY(expr: PtExpression): Boolean {
return when(expr) {
is PtIdentifier -> true
is PtMachineRegister -> true
is PtIrRegister -> true
is PtNumber -> true
is PtBuiltinFunctionCall -> expr.name in arrayOf("lsb", "msb")
else -> false
@ -1454,6 +1594,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
in ByteDatatypes -> {
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE)
asmgen.out("""
cmp #0
beq +
lda #1
+ eor #1""")
@ -1662,8 +1803,8 @@ internal class AssignmentAsmGen(private val program: PtProgram,
when (valueDt) {
DataType.BOOL -> {
if(targetDt in ByteDatatypes) {
// optimization to assign boolean expression to byte target (just assign the 0 or 1 directly, no cast needed)
if(targetDt in IntegerDatatypes) {
// optimization to assign boolean expression to integer target (just assign the 0 or 1 directly)
val assignDirect = AsmAssignment(
AsmAssignSource.fromAstSource(value, program, asmgen),
target,
@ -1672,7 +1813,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
)
assignExpression(assignDirect, target.scope)
} else {
throw AssemblyError("expected bool or byte target type")
TODO("assign bool to non-integer type $targetDt")
}
}
in ByteDatatypes -> {
@ -2182,7 +2323,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc #$constIndex
bne +
bcc +
iny
+""")
}
@ -2207,7 +2348,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
asmgen.out("""
clc
adc P8ZP_SCRATCH_REG
bne +
bcc +
iny
+""")
}
@ -2217,7 +2358,7 @@ internal class AssignmentAsmGen(private val program: PtProgram,
ldy #>$sourceName
clc
adc #<$sourceName
bne +
bcc +
iny
+""")
}

View File

@ -1703,7 +1703,7 @@ $shortcutLabel:""")
private fun inplacemodificationWordWithLiteralval(name: String, dt: DataType, operator: String, value: Int, block: PtBlock?) {
// note: this contains special optimized cases because we know the exact value. Don't replace this with another routine.
inplacemodificationSomeWordWithLiteralval(name, name + "+1", dt, operator, value, block)
inplacemodificationSomeWordWithLiteralval(name, "$name+1", dt, operator, value, block)
}
private fun inplacemodificationSomeWordWithLiteralval(lsb: String, msb: String, dt: DataType, operator: String, value: Int, block: PtBlock?) {

View File

@ -53,7 +53,7 @@ class TestCodegen: FunSpec({
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(Position.DUMMY).also {
val target = PtAssignTarget(false, Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
@ -68,7 +68,7 @@ class TestCodegen: FunSpec({
sub.add(assign)
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
val prefixTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
prefixAssign.add(prefixTarget)
@ -76,7 +76,7 @@ class TestCodegen: FunSpec({
sub.add(prefixAssign)
val numberAssign = PtAugmentedAssign("-=", Position.DUMMY)
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
val numberAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
numberAssign.add(numberAssignTarget)
@ -84,7 +84,7 @@ class TestCodegen: FunSpec({
sub.add(numberAssign)
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
val cxregAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
cxregAssign.add(cxregAssignTarget)

View File

@ -6,9 +6,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@ -29,7 +28,7 @@ dependencies {
implementation project(':codeGenIntermediate')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
}

View File

@ -6,9 +6,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@ -28,9 +27,9 @@ dependencies {
implementation project(':intermediate')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

View File

@ -1,5 +1,7 @@
package prog8.codegen.intermediate
import prog8.code.StRomSub
import prog8.code.StRomSubParameter
import prog8.code.ast.*
import prog8.code.core.*
import prog8.intermediate.*
@ -8,16 +10,73 @@ import prog8.intermediate.*
internal class AssignmentGen(private val codeGen: IRCodeGen, private val expressionEval: ExpressionGen) {
internal fun translate(assignment: PtAssignment): IRCodeChunks {
if(assignment.target.children.single() is PtMachineRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
if(assignment.multiTarget) {
val values = assignment.value as? PtFunctionCall
?: throw AssemblyError("only function calls can return multiple values in a multi-assign")
val chunks = translateRegularAssign(assignment)
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(assignment.position)
return chunks
val sub = codeGen.symbolTable.lookup(values.name) as? StRomSub
?: throw AssemblyError("only asmsubs can return multiple values")
val result = mutableListOf<IRCodeChunkBase>()
val funcCall = this.expressionEval.translate(values)
require(funcCall.multipleResultRegs.size + funcCall.multipleResultFpRegs.size >= 2)
if(funcCall.multipleResultFpRegs.isNotEmpty())
TODO("deal with (multiple?) FP return registers")
val assignmentTargets = assignment.children.dropLast(1)
addToResult(result, funcCall, funcCall.resultReg, funcCall.resultFpReg)
if(sub.returns.size==assignmentTargets.size) {
// Targets and values match. Assign all the things. Skip 'void' targets.
sub.returns.zip(assignmentTargets).zip(funcCall.multipleResultRegs).forEach {
val target = it.first.second as PtAssignTarget
if(!target.void) {
val regNumber = it.second
val returns = it.first.first
result += assignCpuRegister(returns, regNumber, target)
}
}
} else {
throw AssemblyError("number of values and targets don't match")
}
return result
} else {
if (assignment.target.children.single() is PtIrRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
return translateRegularAssign(assignment)
}
}
private fun assignCpuRegister(returns: StRomSubParameter, regNum: Int, target: PtAssignTarget): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val loadCpuRegInstr = when(returns.register.registerOrPair) {
RegisterOrPair.A -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.X -> IRInstruction(Opcode.LOADHX, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.Y -> IRInstruction(Opcode.LOADHY, IRDataType.BYTE, reg1=regNum)
RegisterOrPair.AX -> IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum)
RegisterOrPair.AY -> IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum)
RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum)
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
null -> {
when(returns.register.statusflag) {
Statusflag.Pc -> IRInstruction(Opcode.LOADHA, IRDataType.BYTE, reg1=regNum)
else -> throw AssemblyError("weird statusflag as returnvalue")
}
}
else -> throw AssemblyError("cannot load register")
}
addInstr(result, loadCpuRegInstr, null)
// build an assignment to store the value in the actual target.
val assign = PtAssignment(target.position)
assign.add(target)
assign.add(PtIrRegister(regNum, target.type, target.position))
result += translate(assign)
return result
}
internal fun translate(augAssign: PtAugmentedAssign): IRCodeChunks {
if(augAssign.target.children.single() is PtMachineRegister)
// augmented assignment always has just a single target
if (augAssign.target.children.single() is PtIrRegister)
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
val target = augAssign.target
@ -28,7 +87,8 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val array = target.array
val value = augAssign.value
val signed = target.type in SignedDatatypes
val result = when(augAssign.operator) {
val chunks = when (augAssign.operator) {
"+=" -> operatorPlusInplace(symbol, array, constAddress, memTarget, targetDt, value)
"-=" -> operatorMinusInplace(symbol, array, constAddress, memTarget, targetDt, value)
"*=" -> operatorMultiplyInplace(symbol, array, constAddress, memTarget, targetDt, value)
@ -50,9 +110,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
in PrefixOperators -> inplacePrefix(augAssign.operator, symbol, array, constAddress, memTarget, targetDt)
else -> throw AssemblyError("invalid augmented assign operator ${augAssign.operator}")
}
val chunks = if(result!=null) result else fallbackAssign(augAssign)
} ?: fallbackAssign(augAssign)
chunks.filterIsInstance<IRCodeChunk>().firstOrNull()?.appendSrcPosition(augAssign.position)
return chunks
}
@ -81,7 +139,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
return translateRegularAssign(normalAssign)
}
private fun inplacePrefix(operator: String, symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType): IRCodeChunks? {
private fun inplacePrefix(operator: String, symbol: String?, array: PtArrayIndexer?, constAddress: Int?, memory: PtMemoryByte?, vmDt: IRDataType): IRCodeChunks {
if(operator=="+")
return emptyList()
@ -270,11 +328,11 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
else
throw AssemblyError("assignment value and target dt mismatch")
} else false
if (assignment.value is PtMachineRegister) {
valueRegister = (assignment.value as PtMachineRegister).register
if (assignment.value is PtIrRegister) {
valueRegister = (assignment.value as PtIrRegister).register
if(extendByteToWord) {
valueRegister = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister, reg2=(assignment.value as PtMachineRegister).register), null)
addInstr(result, IRInstruction(Opcode.EXT, IRDataType.BYTE, reg1=valueRegister, reg2=(assignment.value as PtIrRegister).register), null)
}
} else {
val tr = expressionEval.translateExpression(assignment.value)
@ -444,9 +502,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
addToResult(result, tr, tr.resultReg, -1)
return Pair(result, tr.resultReg)
}
val mult: PtExpression
mult = PtBinaryExpression("*", DataType.UBYTE, array.position)
val mult: PtExpression = PtBinaryExpression("*", DataType.UBYTE, array.position)
mult.children += array.index
mult.children += PtNumber(DataType.UBYTE, itemsize.toDouble(), array.position)
val tr = expressionEval.translateExpression(mult)
@ -1334,9 +1390,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
val valueReg = codeGen.registers.nextFree()
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADM, vmDt, reg1=valueReg, labelSymbol = array.variable.name, symbolOffset = constIndex*eltSize)
when(comparisonOperator) {
"==" -> it += IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> it += IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
it += when(comparisonOperator) {
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"<" -> return null // TODO("array <0 inplace")) // TODO?
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
">" -> return null // TODO("array >0 inplace")) // TODO?
@ -1357,9 +1413,9 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
addToResult(result, indexTr, indexTr.resultReg, -1)
result += IRCodeChunk(null, null).also {
it += IRInstruction(Opcode.LOADX, vmDt, reg1=valueReg, reg2=indexTr.resultReg, labelSymbol = array.variable.name)
when(comparisonOperator) {
"==" -> it += IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> it += IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
it += when(comparisonOperator) {
"==" -> IRInstruction(Opcode.SZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"!=" -> IRInstruction(Opcode.SNZ, vmDt, reg1=cmpResultReg, reg2=valueReg)
"<" -> return null // TODO("array <0 inplace")) // TODO?
"<=" -> return null // TODO("array <=0 inplace")) // TODO?
">" -> return null // TODO("array >0 inplace")) // TODO?

View File

@ -132,10 +132,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
val addressTr = exprGen.translateExpression(call.args[0])
addToResult(result, addressTr, addressTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.CALLI, reg1 = addressTr.resultReg), null)
if(call.void)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
return if(call.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else
return ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.nextFree(), -1) // TODO actually the result is returned in CPU registers AY...
ExpressionCodeResult(result, IRDataType.WORD, codeGen.registers.nextFree(), -1) // TODO actually the result is returned in CPU registers AY...
}
private fun funcCallfar(call: PtBuiltinFunctionCall): ExpressionCodeResult {
@ -854,10 +854,10 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
private fun assignRegisterTo(target: PtExpression, register: Int): IRCodeChunks {
val assignment = PtAssignment(target.position)
val assignTarget = PtAssignTarget(target.position)
val assignTarget = PtAssignTarget(false, target.position)
assignTarget.children.add(target)
assignment.children.add(assignTarget)
assignment.children.add(PtMachineRegister(register, target.type, target.position))
assignment.children.add(PtIrRegister(register, target.type, target.position))
val result = mutableListOf<IRCodeChunkBase>()
result += codeGen.translateNode(assignment)
return result

View File

@ -7,8 +7,11 @@ import prog8.code.core.*
import prog8.intermediate.*
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int) {
constructor(chunks: IRCodeChunk, dt: IRDataType, resultReg: Int, resultFpReg: Int) : this(listOf(chunks), dt, resultReg, resultFpReg)
internal class ExpressionCodeResult(val chunks: IRCodeChunks, val dt: IRDataType, val resultReg: Int, val resultFpReg: Int,
val multipleResultRegs: List<Int> = emptyList(), val multipleResultFpRegs: List<Int> = emptyList()
) {
constructor(chunk: IRCodeChunk, dt: IRDataType, resultReg: Int, resultFpReg: Int, multipleResultRegs: List<Int> = emptyList(), multipleResultFpRegs: List<Int> = emptyList())
: this(listOf(chunk), dt, resultReg, resultFpReg, multipleResultRegs, multipleResultFpRegs)
companion object {
val EMPTY: ExpressionCodeResult = ExpressionCodeResult(emptyList(), IRDataType.BYTE, -1, -1)
@ -24,7 +27,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
fun translateExpression(expr: PtExpression): ExpressionCodeResult {
return when (expr) {
is PtMachineRegister -> {
is PtIrRegister -> {
ExpressionCodeResult(emptyList(), irType(expr.type), expr.register, -1)
}
is PtBool -> {
@ -463,7 +466,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
argRegisters.add(FunctionCallArgs.ArgumentSpec(parameter.name, null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, null)))
result += tr.chunks
}
// return value
// return value (always singular for normal Subs)
val returnRegSpec = if(fcall.void) null else {
val returnIrType = irType(callTarget.returnType!!)
if(returnIrType==IRDataType.FLOAT)
@ -472,7 +475,8 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), null)
}
// create the call
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec)), null)
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name,
fcallArgs = FunctionCallArgs(argRegisters, if(returnRegSpec==null) emptyList() else listOf(returnRegSpec))), null)
return if(fcall.void)
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
else if(fcall.type==DataType.FLOAT)
@ -493,44 +497,57 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
else
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, parameter.register)))
result += tr.chunks
when(parameter.register.registerOrPair) {
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=tr.resultReg), null)
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.STOREHX, IRDataType.BYTE, reg1=tr.resultReg), null)
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.STOREHY, IRDataType.BYTE, reg1=tr.resultReg), null)
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=tr.resultReg), null)
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=tr.resultReg), null)
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=tr.resultReg), null)
in Cx16VirtualRegisters -> {
addInstr(result, IRInstruction(Opcode.STOREM, paramDt, reg1=tr.resultReg, labelSymbol = "cx16.${parameter.register.registerOrPair.toString().lowercase()}"), null)
}
null -> when(parameter.register.statusflag) {
// TODO: do the statusflag argument as last
Statusflag.Pc -> addInstr(result, IRInstruction(Opcode.LSR, paramDt, reg1=tr.resultReg), null)
else -> throw AssemblyError("weird statusflag as param")
}
else -> throw AssemblyError("unsupported register arg")
}
}
// return value
var statusFlagResult: Statusflag? = null
if(callTarget.returns.size>1)
return callRomSubWithMultipleReturnValues(callTarget, fcall, argRegisters, result)
// return a single value (or nothing)
val returnRegSpec = if(fcall.void) null else {
if(callTarget.returns.isEmpty())
null
else if(callTarget.returns.size==1) {
else {
val returns = callTarget.returns[0]
val returnIrType = irType(returns.type)
if(returnIrType==IRDataType.FLOAT)
if (returnIrType == IRDataType.FLOAT)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register)
else {
statusFlagResult = returns.register.statusflag
val returnRegister = if(statusFlagResult==null) codeGen.registers.nextFree() else -1
val returnRegister = codeGen.registers.nextFree()
FunctionCallArgs.RegSpec(returnIrType, returnRegister, returns.register)
}
} else {
// multiple return values: take the first *register* (not status flag) return value and ignore the rest.
val returns = callTarget.returns.first { it.register.registerOrPair!=null }
val returnIrType = irType(returns.type)
if(returnIrType==IRDataType.FLOAT)
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFreeFloat(), returns.register)
else
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.nextFree(), returns.register)
}
}
// create the call
val returnRegs = if(returnRegSpec==null) emptyList() else listOf(returnRegSpec)
val call =
if(callTarget.address==null)
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
else
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegSpec))
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegs))
addInstr(result, call, null)
var finalReturnRegister = returnRegSpec?.registerNum ?: -1
if(fcall.parent is PtAssignment || fcall.parent is PtTypeCast) {
// look if the status flag bit should actually be returned as a 0/1 byte value in a result register (so it can be assigned)
if(statusFlagResult!=null && returnRegSpec!=null) {
val statusFlagResult = returnRegSpec?.cpuRegister?.statusflag
if(statusFlagResult!=null) {
// assign status flag bit to the return value register
finalReturnRegister = returnRegSpec.registerNum
if(finalReturnRegister<0)
@ -573,6 +590,29 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
}
}
private fun callRomSubWithMultipleReturnValues(
callTarget: StRomSub,
fcall: PtFunctionCall,
argRegisters: MutableList<FunctionCallArgs.ArgumentSpec>,
result: MutableList<IRCodeChunkBase>
): ExpressionCodeResult {
// return multiple values
val returnRegisters = callTarget.returns.map {
val regnum = if(it.type==DataType.FLOAT) codeGen.registers.nextFreeFloat() else codeGen.registers.nextFree()
FunctionCallArgs.RegSpec(irType(it.type), regnum, it.register)
}
// create the call
val call =
if(callTarget.address==null)
IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
else
IRInstruction(Opcode.CALL, address = callTarget.address!!.toInt(), fcallArgs = FunctionCallArgs(argRegisters, returnRegisters))
addInstr(result, call, null)
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum}
val resultFpRegs = returnRegisters.filter{it.dt==IRDataType.FLOAT}.map{it.registerNum}
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1, resultRegs, resultFpRegs)
}
private fun operatorGreaterThan(
binExpr: PtBinaryExpression,
vmDt: IRDataType,
@ -746,17 +786,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
val result = mutableListOf<IRCodeChunkBase>()
val tr = translateExpression(binExpr.left)
addToResult(result, tr, tr.resultReg, -1)
return if(binExpr.right is PtNumber) {
addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else if(binExpr.right is PtBool) {
addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtBool).asInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
} else {
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.XORR, vmDt, reg1 = tr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
return when (binExpr.right) {
is PtNumber -> {
addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtNumber).number.toInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
}
is PtBool -> {
addInstr(result, IRInstruction(Opcode.XOR, vmDt, reg1 = tr.resultReg, immediate = (binExpr.right as PtBool).asInt()), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
}
else -> {
val rightTr = translateExpression(binExpr.right)
addToResult(result, rightTr, rightTr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.XORR, vmDt, reg1 = tr.resultReg, reg2 = rightTr.resultReg), null)
ExpressionCodeResult(result, vmDt, tr.resultReg, -1)
}
}
}

View File

@ -52,9 +52,6 @@ class IRCodeGen(
optimizer.optimize(options.optimize, errors)
irProg.validate()
val regOptimizer = IRRegisterOptimizer(irProg)
regOptimizer.optimize()
return irProg
}
@ -394,10 +391,10 @@ class IRCodeGen(
val result = mutableListOf<IRCodeChunkBase>()
when(iterable) {
is PtRange -> {
if(iterable.from is PtNumber && iterable.to is PtNumber)
result += translateForInConstantRange(forLoop, loopvar)
result += if(iterable.from is PtNumber && iterable.to is PtNumber)
translateForInConstantRange(forLoop, loopvar)
else
result += translateForInNonConstantRange(forLoop, loopvar)
translateForInNonConstantRange(forLoop, loopvar)
}
is PtIdentifier -> {
require(forLoop.variable.name == loopvar.scopedName)
@ -794,6 +791,7 @@ class IRCodeGen(
}
else if(pow2>=1 &&!signed) {
// just shift multiple bits
// TODO also try to optimize for signed division by powers of 2
val pow2reg = registers.nextFree()
code += IRInstruction(Opcode.LOAD, dt, reg1=pow2reg, immediate = pow2)
code += if(signed)
@ -1451,6 +1449,10 @@ class IRCodeGen(
}
when(val cond=ifElse.condition) {
is PtBool -> {
// normally this will be optimized away, but not with -noopt
translateSimple(cond, Opcode.BSTEQ)
}
is PtTypeCast -> {
require(cond.type==DataType.BOOL && cond.value.type in NumericDatatypes)
translateSimple(cond, Opcode.BSTEQ)
@ -1502,17 +1504,16 @@ class IRCodeGen(
private fun translate(jump: PtJump): IRCodeChunks {
val result = mutableListOf<IRCodeChunkBase>()
val chunk = IRCodeChunk(null, null)
if(jump.address!=null) {
chunk += IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
chunk += if(jump.address!=null) {
IRInstruction(Opcode.JUMP, address = jump.address!!.toInt())
} else {
if (jump.identifier != null) {
if(isIndirectJump(jump)) {
chunk += IRInstruction(Opcode.JUMPI, labelSymbol = jump.identifier!!.name)
IRInstruction(Opcode.JUMPI, labelSymbol = jump.identifier!!.name)
} else {
chunk += IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
}
}
else
} else
throw AssemblyError("weird jump")
}
result += chunk
@ -1650,7 +1651,8 @@ class IRCodeGen(
val args = params.map { (dt, reg)->
FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(dt, reg, null))
}
val returnSpec = if(returns==null) null else FunctionCallArgs.RegSpec(returns.first, returns.second, null)
// for now, syscalls have 0 or 1 return value
val returnSpec = if(returns==null) emptyList() else listOf(FunctionCallArgs.RegSpec(returns.first, returns.second, null))
it += IRInstruction(Opcode.SYSCALL, immediate = syscall.number, fcallArgs = FunctionCallArgs(args, returnSpec))
}
}

View File

@ -52,15 +52,12 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
|| removeWeirdBranches(chunk1, chunk2, indexedInstructions)
|| removeDoubleSecClc(chunk1, indexedInstructions)
|| cleanupPushPop(chunk1, indexedInstructions)
// TODO other optimizations
} while (changed)
}
}
removeEmptyChunks(sub)
}
// TODO also do register optimization step here at the end?
irprog.linkChunks() // re-link
}
@ -182,8 +179,7 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
chunks += sub.chunks[0]
for(ix in 1 until sub.chunks.size) {
val lastChunk = chunks.last()
val candidate = sub.chunks[ix]
when(candidate) {
when(val candidate = sub.chunks[ix]) {
is IRCodeChunk -> {
if(mayJoinCodeChunks(lastChunk, candidate)) {
lastChunk.instructions += candidate.instructions
@ -433,21 +429,34 @@ class IRPeepholeOptimizer(private val irprog: IRProgram) {
}
private fun removeDoubleLoadsAndStores(chunk: IRCodeChunk, indexedInstructions: List<IndexedValue<IRInstruction>>): Boolean {
return false
/*
var changed = false
indexedInstructions.forEach { (idx, ins) ->
if(ins.opcode==Opcode.STOREM) {
val prev = indexedInstructions[idx-1].value
if(prev.opcode==Opcode.LOADM) {
// loadm.X rX,something | storem.X rX,something ?? -> get rid of the store.
if(ins.labelSymbol!=null && ins.labelSymbol==prev.labelSymbol && ins.labelSymbolOffset==prev.labelSymbolOffset) {
changed=true
chunk.instructions.removeAt(idx)
}
else if(ins.address!=null && ins.address==prev.address) {
changed=true
chunk.instructions.removeAt(idx)
}
}
}
// TODO: detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
// TODO: detect multiple stores to the same target, only keep first (if target is not I/O memory)
// TODO: detect multiple float ffrom/fto to the same target, only keep first
// TODO: detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
// TODO: detect multiple same ands, ors; only keep first
// TODO: (hard) detect multiple registers being assigned the same value (and not changed) - use only 1 of them
/*
Possible other optimizations:
// detect multiple loads to the same target registers, only keep first (if source is not I/O memory)
// detect multiple stores to the same target, only keep first (if target is not I/O memory)
// detect multiple float ffrom/fto to the same target, only keep first
// detect subsequent same xors/nots/negs, remove the pairs completely as they cancel out
// detect multiple same ands, ors; only keep first
// detect multiple registers being assigned the same value (and not changed) - use only 1 of them (hard!)
// ...
*/
}
return changed
*/
}
}

View File

@ -1,103 +0,0 @@
package prog8.codegen.intermediate
import prog8.intermediate.IRProgram
class IRRegisterOptimizer(private val irProg: IRProgram) {
fun optimize() {
// reuseRegisters()
}
/*
TODO: this register re-use renumbering isn't going to work like this,
because subroutines will be clobbering the registers that the subroutine
which is calling them might be using...
private fun reuseRegisters() {
fun addToUsage(usage: MutableMap<Pair<Int, IRDataType>, MutableSet<IRCodeChunkBase>>,
regnum: Int,
dt: IRDataType,
chunk: IRCodeChunkBase) {
val key = regnum to dt
val chunks = usage[key] ?: mutableSetOf()
chunks.add(chunk)
usage[key] = chunks
}
val usage: MutableMap<Pair<Int, IRDataType>, MutableSet<IRCodeChunkBase>> = mutableMapOf()
irProg.foreachCodeChunk { chunk ->
chunk.usedRegisters().regsTypes.forEach { (regNum, types) ->
types.forEach { dt ->
addToUsage(usage, regNum, dt, chunk)
}
}
}
val registerReplacements = usage.asSequence()
.filter { it.value.size==1 }
.map { it.key to it.value.iterator().next() }
.groupBy({ it.second }, {it.first})
.asSequence()
.associate { (chunk, registers) ->
chunk to registers.withIndex().associate { (index, reg) -> reg to 50000+index }
}
registerReplacements.forEach { replaceRegisters(it.key, it.value) }
}
private fun replaceRegisters(chunk: IRCodeChunkBase, replacements: Map<Pair<Int, IRDataType>, Int>) {
val (rF, rI) = replacements.asSequence().partition { it.key.second==IRDataType.FLOAT }
val replacementsInt = rI.associate { it.key.first to it.value }
val replacementsFloat = rF.associate { it.key.first to it.value }
fun replaceRegs(fcallArgs: FunctionCallArgs?): FunctionCallArgs? {
if(fcallArgs==null)
return null
val args = if(fcallArgs.arguments.isEmpty()) fcallArgs.arguments else {
fcallArgs.arguments.map {
FunctionCallArgs.ArgumentSpec(
it.name,
it.address,
FunctionCallArgs.RegSpec(
it.reg.dt,
if(it.reg.dt==IRDataType.FLOAT)
replacementsFloat.getOrDefault(it.reg.registerNum, it.reg.registerNum)
else
replacementsInt.getOrDefault(it.reg.registerNum, it.reg.registerNum),
it.reg.cpuRegister
)
)
}
}
val rt = fcallArgs.returns
val returns = if(rt==null) null else {
FunctionCallArgs.RegSpec(
rt.dt,
if(rt.dt==IRDataType.FLOAT)
replacementsFloat.getOrDefault(rt.registerNum, rt.registerNum)
else
replacementsInt.getOrDefault(rt.registerNum, rt.registerNum),
rt.cpuRegister
)
}
return FunctionCallArgs(args, returns)
}
fun replaceRegs(instruction: IRInstruction): IRInstruction {
val reg1 = replacementsInt.getOrDefault(instruction.reg1, instruction.reg1)
val reg2 = replacementsInt.getOrDefault(instruction.reg2, instruction.reg2)
val fpReg1 = replacementsFloat.getOrDefault(instruction.fpReg1, instruction.fpReg1)
val fpReg2 = replacementsFloat.getOrDefault(instruction.fpReg2, instruction.fpReg2)
return instruction.copy(reg1 = reg1, reg2 = reg2, fpReg1 = fpReg1, fpReg2 = fpReg2, fcallArgs = replaceRegs(instruction.fcallArgs))
}
val newInstructions = chunk.instructions.map {
replaceRegs(it)
}
chunk.instructions.clear()
chunk.instructions.addAll(newInstructions)
}
*/
}

View File

@ -50,7 +50,7 @@ class TestVmCodeGen: FunSpec({
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
val assign = PtAugmentedAssign("+=", Position.DUMMY)
val target = PtAssignTarget(Position.DUMMY).also {
val target = PtAssignTarget(false, Position.DUMMY).also {
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
@ -65,7 +65,7 @@ class TestVmCodeGen: FunSpec({
sub.add(assign)
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
val prefixTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
prefixAssign.add(prefixTarget)
@ -73,7 +73,7 @@ class TestVmCodeGen: FunSpec({
sub.add(prefixAssign)
val numberAssign = PtAugmentedAssign("+=", Position.DUMMY)
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
val numberAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
numberAssign.add(numberAssignTarget)
@ -81,7 +81,7 @@ class TestVmCodeGen: FunSpec({
sub.add(numberAssign)
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
val cxregAssignTarget = PtAssignTarget(false, Position.DUMMY).also {
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
}
cxregAssign.add(cxregAssignTarget)

View File

@ -6,9 +6,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@ -27,7 +26,7 @@ dependencies {
implementation project(':codeCore')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
}

View File

@ -435,7 +435,6 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
subrightIsConst: Boolean): IAstModification?
{
// NOTE: THESE REORDERINGS ARE ONLY VALID FOR FLOATING POINT CONSTANTS
// TODO: this implements only a small set of possible reorderings at this time, we could perhaps add more
if(expr.operator==subExpr.operator) {
// both operators are the same.

View File

@ -75,10 +75,11 @@ class VarConstantValueTypeAdjuster(
val declValue = decl.value?.constValue(program)
if (declValue != null) {
// variable is never written to, so it can be replaced with a constant, IF the value is a constant
errors.info("variable is never written to and was replaced by a constant", decl.position)
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, declValue, decl.sharedWithAsm, decl.splitArray, decl.position)
decl.value = null
return listOf(
IAstModification.ReplaceNode(decl, const, parent),
IAstModification.ReplaceNode(decl, const, parent)
)
}
}
@ -94,7 +95,7 @@ class VarConstantValueTypeAdjuster(
)
}
// variable only has a single write and it is the initialization value, so it can be replaced with a constant, IF the value is a constant
errors.info("variable is never written to and was replaced by a constant", decl.position)
errors.info("variable '${decl.name}' is never written to and was replaced by a constant", decl.position)
val const = VarDecl(VarDeclType.CONST, decl.origin, decl.datatype, decl.zeropage, decl.arraysize, decl.name, decl.names, singleAssignment.value, decl.sharedWithAsm, decl.splitArray, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, const, parent),
@ -103,15 +104,15 @@ class VarConstantValueTypeAdjuster(
}
}
/*
TODO: need to check if there are no variable usages between the declaration and the assignment (because these rely on the original initialization value)
if(writes.size==2) {
val firstAssignment = writes[0].parent as? Assignment
val secondAssignment = writes[1].parent as? Assignment
if(firstAssignment?.origin==AssignmentOrigin.VARINIT && secondAssignment?.value?.constValue(program)!=null) {
errors.warn("variable is only assigned once here, consider using this as the initialization value in the declaration instead", secondAssignment.position)
TODO: need to check if there are no variable usages between the declaration and the assignment (because these rely on the original initialization value)
if(writes.size==2) {
val firstAssignment = writes[0].parent as? Assignment
val secondAssignment = writes[1].parent as? Assignment
if(firstAssignment?.origin==AssignmentOrigin.VARINIT && secondAssignment?.value?.constValue(program)!=null) {
errors.warn("variable is only assigned once here, consider using this as the initialization value in the declaration instead", secondAssignment.position)
}
}
}
*/
*/
}
return noModifications
@ -153,11 +154,10 @@ class VarConstantValueTypeAdjuster(
if(func==listOf("clamp")) {
val t1 = functionCallExpr.args[0].inferType(program)
if(t1.isKnown) {
val replaceFunc: String
if(t1.isBytes) {
replaceFunc = if(t1.istype(DataType.BYTE)) "clamp__byte" else "clamp__ubyte"
val replaceFunc = if(t1.isBytes) {
if(t1.istype(DataType.BYTE)) "clamp__byte" else "clamp__ubyte"
} else if(t1.isInteger) {
replaceFunc = if(t1.istype(DataType.WORD)) "clamp__word" else "clamp__uword"
if(t1.istype(DataType.WORD)) "clamp__word" else "clamp__uword"
} else {
errors.err("clamp builtin not supported for floats, use floats.clamp", functionCallExpr.position)
return noModifications
@ -309,7 +309,7 @@ internal class ConstantIdentifierReplacer(
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, identifier.position)
listOf(IAstModification.ReplaceNode(arrayIdx.parent, assignTarget, arrayIdx.parent.parent))
} else {
val memread = DirectMemoryRead(add, identifier.position)

View File

@ -13,8 +13,6 @@ import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has?
class ExpressionSimplifier(private val program: Program, private val options: CompilationOptions, private val errors: IErrorReporter) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
@ -242,10 +240,13 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
// optimize boolean constant comparisons
if(expr.operator=="==") {
if(rightDt==DataType.BOOL && leftDt==DataType.BOOL) {
if(rightVal?.asBooleanValue==true)
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
else
return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
val rightConstBool = rightVal?.asBooleanValue
if(rightConstBool!=null) {
return if(rightConstBool)
listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
else
listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
}
}
if (rightVal?.number == 1.0) {
if (options.strictBool) {
@ -740,7 +741,7 @@ class ExpressionSimplifier(private val program: Program, private val options: Co
in powersOfTwo -> {
if (leftDt==DataType.UBYTE || leftDt==DataType.UWORD) {
// Unsigned number divided by a power of two => shift right
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values), TODO is this correct???
// Signed number can't simply be bitshifted in this case (due to rounding issues for negative values),
// so we leave that as is and let the code generator deal with it.
val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)

View File

@ -162,7 +162,7 @@ class StatementOptimizer(private val program: Program,
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@ -177,7 +177,7 @@ class StatementOptimizer(private val program: Program,
val character = options.compTarget.encodeString(sv.value, sv.encoding)[0]
val byte = NumericLiteral(DataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
@ -190,7 +190,7 @@ class StatementOptimizer(private val program: Program,
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
AssignTarget(forLoop.loopVar, null, null, null, false, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
@ -206,7 +206,7 @@ class StatementOptimizer(private val program: Program,
val pos = forLoop.position
val loopVar = forLoop.loopVar
val addSubOne = BinaryExpression(loopVar.copy(), if(inc) "+" else "-", NumericLiteral.optimalInteger(1, pos), pos, false)
return Assignment(AssignTarget(loopVar.copy(), null, null, pos), addSubOne, AssignmentOrigin.USERCODE, pos)
return Assignment(AssignTarget(loopVar.copy(), null, null, null, false, pos), addSubOne, AssignmentOrigin.USERCODE, pos)
}
if (range != null && range.to.constValue(program)?.number == 0.0 && range.step.constValue(program)?.number==-1.0) {
@ -219,7 +219,7 @@ class StatementOptimizer(private val program: Program,
val decOne = incOrDec(false)
forLoop.body.statements.add(decOne)
val replacement = AnonymousScope(mutableListOf(
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, pos),
Assignment(AssignTarget(forLoop.loopVar.copy(), null, null, null, false, pos),
fromExpr, AssignmentOrigin.OPTIMIZER, pos),
UntilLoop(forLoop.body, condition, pos)
), pos)
@ -454,12 +454,6 @@ class StatementOptimizer(private val program: Program,
override fun after(whenStmt: When, parent: Node): Iterable<IAstModification> {
fun replaceWithIf(condition: Expression, trueBlock: AnonymousScope, elseBlock: AnonymousScope?): List<IAstModification> {
val ifStmt = IfElse(condition, trueBlock, elseBlock ?: AnonymousScope(mutableListOf(), whenStmt.position), whenStmt.position)
errors.info("for boolean condition a normal if statement is preferred", whenStmt.position)
return listOf(IAstModification.ReplaceNode(whenStmt, ifStmt, parent))
}
val constantValue = whenStmt.condition.constValue(program)?.number
if(constantValue!=null) {
// when condition is a constant

View File

@ -69,14 +69,14 @@ class UnusedCodeRemover(private val program: Program,
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars) {
if(block.name != internedStringsModuleName) {
if(block.name != internedStringsModuleName && "ignore_unused" !in block.options()) {
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.info("removing unused block '${block.name}'", block.position)
}
return listOf(IAstModification.Remove(block, parent as IStatementContainer))
}
if(callgraph.unused(block)) {
if(block.statements.any{ it !is VarDecl || it.type== VarDeclType.VAR}) {
if(block.statements.any{ it !is VarDecl || it.type== VarDeclType.VAR} && "ignore_unused" !in block.options()) {
if(!block.statements.any { it is Subroutine && it.hasBeenInlined })
errors.info("removing unused block '${block.name}'", block.position)
}

View File

@ -7,9 +7,8 @@ plugins {
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
targetCompatibility = JavaLanguageVersion.of(javaVersion)
sourceCompatibility = JavaLanguageVersion.of(javaVersion)
}
compileKotlin {
@ -36,11 +35,11 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.6'
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.20"
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.0"
testImplementation project(':codeCore')
testImplementation project(':intermediate')
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.8.0'
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.9.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

View File

@ -3,7 +3,6 @@
%import syslib
%import conv
%import shared_textio_functions
txt {
@ -17,6 +16,10 @@ sub clear_screen() {
chrout(125)
}
sub cls() {
chrout(125)
}
sub nl() {
chrout('\n')
}
@ -148,15 +151,15 @@ _tmp_outchar
}
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y
; ---- print zero terminated string from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
- lda (P8ZP_SCRATCH_B1),y
- lda (P8ZP_SCRATCH_W2),y
beq +
jsr chrout
iny
@ -168,13 +171,13 @@ asmsub print (str text @ AY) clobbers(A,Y) {
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
jsr conv.internal_ubyte2decimal
pha
tya
jsr chrout
pla
jsr chrout
txa
jsr chrout
pla
jmp chrout
}}
}
@ -182,21 +185,21 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
jsr conv.ubyte2decimal
jsr conv.internal_ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr chrout
pla
txa
jsr chrout
jmp _ones
+ pla
cmp #'0'
+ cpx #'0'
beq _ones
txa
jsr chrout
_ones txa
_ones pla
jmp chrout
}}
}
@ -210,7 +213,7 @@ asmsub print_b (byte value @ A) clobbers(A,X,Y) {
lda #'-'
jsr chrout
+ pla
jsr conv.byte2decimal
jsr conv.internal_byte2decimal
jmp print_ub._print_byte_digits
}}
}
@ -223,7 +226,7 @@ asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
lda #'$'
jsr chrout
pla
+ jsr conv.ubyte2hex
+ jsr conv.internal_ubyte2hex
jsr chrout
tya
jmp chrout
@ -277,9 +280,9 @@ asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
jsr conv.internal_uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
- lda conv.internal_uword2decimal.decTenThousands,y
beq +
jsr chrout
iny
@ -291,9 +294,9 @@ asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
jsr conv.uword2decimal
jsr conv.internal_uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
- lda conv.internal_uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
@ -303,7 +306,7 @@ asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
_gotdigit
jsr chrout
iny
lda conv.uword2decimal.decTenThousands,y
lda conv.internal_uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero

View File

@ -93,11 +93,12 @@ romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
romsub $FFED = SCRORG() -> ubyte @ X, ubyte @ Y ; get current window dimensions into X (columns) and Y (rows) NOTE: changed behavior compared to VIC/C64/PET SCREEN() routine!
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> 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
@ -105,15 +106,20 @@ romsub $FFF3 = IOBASE() -> uword @ XY ; read base addr
; ---- utilities -----
asmsub STOP2() clobbers(X) -> bool @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.
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
; -- just like STOP, but omits the special keys result value in A.
; just for convenience because most of the times you're only interested in the stop pressed or not status.
%asm {{
jsr cbm.STOP
beq +
lda #0
rts
+ lda #1
rts
}}
}
inline asmsub GETIN2() clobbers(X,Y) -> ubyte @A {
; -- just like GETIN, but omits the carry flag result value.
; just for convenience because GETIN is so often used to just read keyboard input,
; where you don't have to deal with a potential error status
%asm {{
jsr cbm.GETIN
}}
}

View File

@ -4,7 +4,7 @@
%import syslib
%import conv
%import shared_textio_functions
%import shared_cbm_textio_functions
txt {
@ -13,11 +13,17 @@ txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
@ -288,216 +294,6 @@ _scroll_screen ; scroll only the screen memory
}}
}
romsub $FFD2 = chrout(ubyte char @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string, in PETSCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
pha
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
txa
jmp cbm.CHROUT
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr cbm.CHROUT
_ones txa
jmp cbm.CHROUT
}}
}
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
pha
cmp #0
bpl +
lda #'-'
jsr cbm.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
bcc +
pha
lda #'$'
jsr cbm.CHROUT
pla
+ jsr conv.ubyte2hex
jsr cbm.CHROUT
tya
jmp cbm.CHROUT
}}
}
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr cbm.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr cbm.CHROUT
dey
bne -
rts
}}
}
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr cbm.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp cbm.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr cbm.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr cbm.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
; ---- sets the character in the screen matrix at the given position
%asm {{

View File

@ -4,11 +4,7 @@
%import shared_floats_functions
floats {
; ---- this block contains C-64 floating point related functions ----
const float π = 3.141592653589793
const float PI = π
const float TWOPI = 2*π
; ---- this block contains C-64 floating point related functions ----
; ---- C64 basic and kernal ROM float constants and functions ----
@ -24,7 +20,7 @@ romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value f
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
romsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
romsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
@ -49,7 +45,7 @@ romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/2
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive! You have to fix sign manually!
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
@ -67,7 +63,7 @@ romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mfl
romsub $bd7e = FINLOG(byte value @A) clobbers (A, X, Y) ; fac1 += signed byte in A
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
romsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive

View File

@ -94,23 +94,29 @@ romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> 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
asmsub STOP2() clobbers(X) -> bool @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.
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
; -- just like STOP, but omits the special keys result value in A.
; just for convenience because most of the times you're only interested in the stop pressed or not status.
%asm {{
jsr cbm.STOP
beq +
lda #0
rts
+ lda #1
rts
}}
}
inline asmsub GETIN2() clobbers(X,Y) -> ubyte @A {
; -- just like GETIN, but omits the carry flag result value.
; just for convenience because GETIN is so often used to just read keyboard input,
; where you don't have to deal with a potential error status
%asm {{
jsr cbm.GETIN
}}
}

View File

@ -4,7 +4,7 @@
%import syslib
%import conv
%import shared_textio_functions
%import shared_cbm_textio_functions
txt {
@ -13,11 +13,16 @@ txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
@ -292,216 +297,6 @@ _scroll_screen ; scroll only the screen memory
}}
}
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string, in PESCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
pha
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
txa
jmp cbm.CHROUT
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr cbm.CHROUT
_ones txa
jmp cbm.CHROUT
}}
}
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
pha
cmp #0
bpl +
lda #'-'
jsr cbm.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
bcc +
pha
lda #'$'
jsr cbm.CHROUT
pla
+ jsr conv.ubyte2hex
jsr cbm.CHROUT
tya
jmp cbm.CHROUT
}}
}
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr cbm.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr cbm.CHROUT
dey
bne -
rts
}}
}
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr cbm.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp cbm.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr cbm.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr cbm.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
; ---- sets the character in the screen matrix at the given position
%asm {{

View File

@ -8,97 +8,79 @@ conv {
str @shared string_out = "????????????????" ; result buffer for the string conversion routines
asmsub str_ub0 (ubyte value @ A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_ub0(ubyte value @A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)
%asm {{
jsr internal_ubyte2decimal
sty conv.string_out
stx conv.string_out+1
sta conv.string_out+2
lda #0
sta conv.string_out+3
lda #<conv.string_out
ldy #>conv.string_out
rts
}}
}
asmsub str_ub (ubyte value @ A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
ldy #0
sty P8ZP_SCRATCH_B1
jsr ubyte2decimal ; result in Y/A/X (100s, 10s, 1s).
; hundreds?
cpy #'0'
beq +
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
jmp _done
; tens?
+ cmp #'0'
beq +
sta string_out
stx string_out+1
lda #0
sta string_out+2
jmp _done
+ ; ones.
stx string_out
lda #0
sta string_out+1
_done lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_ub(ubyte value @A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
jsr internal_ubyte2decimal
cpy #'0'
beq +
sty conv.string_out
stx conv.string_out+1
sta conv.string_out+2
lda #0
sta conv.string_out+3
jmp _done
+ cpx #'0'
beq +
stx conv.string_out
sta conv.string_out+1
lda #0
sta conv.string_out+2
jmp _done
+ sta conv.string_out
lda #0
sta conv.string_out+1
_done lda #<conv.string_out
ldy #>conv.string_out
rts
}}
}
asmsub str_b (byte value @ A) clobbers(X) -> str @AY {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
ldy #0
cmp #0
bpl +
ldy #'-'
sty string_out
ldy #1
+ sty P8ZP_SCRATCH_REG
jsr conv.byte2decimal ; result in Y/A/X (100s, 10s, 1s). and in uword2decimal.decHundreds, decTens, decOnes.
; hundreds?
cpy #'0'
bne _out_hundreds
ldy P8ZP_SCRATCH_REG
cmp #'0'
bne _out_tens
beq _out_ones
_out_hundreds
ldy P8ZP_SCRATCH_REG
lda uword2decimal.decHundreds
sta string_out,y
iny
_out_tens
lda uword2decimal.decTens
sta string_out,y
iny
_out_ones
lda uword2decimal.decOnes
sta string_out,y
iny
lda #0
sta string_out,y
lda #<string_out
ldy #>string_out
rts
}}
}
asmsub str_b(byte value @A) clobbers(X) -> str @AY {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
cmp #0
bpl str_ub
eor #255
clc
adc #1
jsr str_ub
; insert a minus sign at the start
lda #0
sta conv.string_out+4
lda conv.string_out+2
sta conv.string_out+3
lda conv.string_out+1
sta conv.string_out+2
lda conv.string_out
sta conv.string_out+1
lda #'-'
sta conv.string_out
lda #<conv.string_out
ldy #>conv.string_out
rts
}}
}
asmsub str_ubhex (ubyte value @ A) clobbers(X) -> str @AY {
; ---- convert the ubyte in A in hex string form
%asm {{
jsr conv.ubyte2hex
jsr internal_ubyte2hex
sta string_out
sty string_out+1
lda #0
@ -158,11 +140,11 @@ asmsub str_uwhex (uword value @ AY) -> str @AY {
%asm {{
pha
tya
jsr conv.ubyte2hex
jsr internal_ubyte2hex
sta string_out
sty string_out+1
pla
jsr conv.ubyte2hex
jsr internal_ubyte2hex
sta string_out+2
sty string_out+3
lda #0
@ -176,9 +158,9 @@ asmsub str_uwhex (uword value @ AY) -> str @AY {
asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
jsr conv.internal_uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
- lda conv.internal_uword2decimal.decTenThousands,y
sta string_out,y
beq +
iny
@ -193,11 +175,11 @@ asmsub str_uw0 (uword value @ AY) clobbers(X) -> str @AY {
asmsub str_uw (uword value @ AY) clobbers(X) -> str @AY {
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
%asm {{
jsr conv.uword2decimal
jsr conv.internal_uword2decimal
ldx #0
_output_digits
ldy #0
- lda conv.uword2decimal.decTenThousands,y
- lda internal_uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
@ -206,7 +188,7 @@ _output_digits
_gotdigit sta string_out,x
inx
iny
lda conv.uword2decimal.decTenThousands,y
lda internal_uword2decimal.decTenThousands,y
bne _gotdigit
_end lda #0
sta string_out,x
@ -238,7 +220,7 @@ asmsub str_w (word value @ AY) clobbers(X) -> str @AY {
adc #1
bcc +
iny
+ jsr conv.uword2decimal
+ jsr conv.internal_uword2decimal
ldx #1
bne str_uw._output_digits
rts
@ -533,17 +515,25 @@ _stop
; ----- low level number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
jmp uword2decimal.hex_try200
}}
asmsub internal_ubyte2decimal(ubyte value @A) -> ubyte @Y, ubyte @X, ubyte @A {
%asm {{
ldy #'0'-1
ldx #'9'+1
sec
- iny
sbc #100
bcs -
- dex
adc #10
bmi -
adc #'0'-1
rts
}}
}
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
asmsub internal_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
; output in internal_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
@ -716,7 +706,7 @@ decOnes .byte 0
}}
}
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
asmsub internal_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 {{
@ -725,11 +715,11 @@ asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
eor #255
clc
adc #1
+ jmp ubyte2decimal
+ jmp internal_ubyte2decimal
}}
}
asmsub ubyte2hex (ubyte value @A) clobbers(X) -> ubyte @A, ubyte @Y {
asmsub internal_ubyte2hex (ubyte value @A) clobbers(X) -> ubyte @A, ubyte @Y {
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
%asm {{
pha
@ -749,7 +739,7 @@ _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
}}
}
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
asmsub internal_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

View File

@ -170,6 +170,7 @@ save_end:
sub set_bpp(ubyte bpp) {
ubyte[8] depths = [0,1,1,2,2,2,2,3]
vera_colordepth = depths[bpp-1]
bitsperpixel = bpp
}
sub set_vera_colordepth(ubyte depth) {
@ -234,7 +235,7 @@ save_end:
sub read_scanline(uword size) {
while size!=0 {
cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
void, cx16.r0 = cx16.MACPTR(min(255, size) as ubyte, &cx16.VERA_DATA0, true)
if_cs {
; no MACPTR support
repeat size
@ -296,7 +297,7 @@ save_end:
cx16.r0L = lsb(size)
if msb(size)!=0
cx16.r0L = 0 ; 256 bytes
cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
void, cx16.r0 = cx16.MCIOUT(cx16.r0L, &cx16.VERA_DATA0, true)
if_cs {
; no MCIOUT support
repeat size

View File

@ -32,6 +32,19 @@ diskio {
cbm.CHKOUT(WRITE_IO_CHANNEL)
}
sub fastmode(ubyte mode) -> bool {
; -- set fast serial mode (0=none, 1=auto_tx, 2=fast writes, 3=both) for the SD card.
; Returns success status (fails on emulator host fs for example)
list_filename[0] = 'u'
list_filename[1] = '0'
list_filename[2] = '>'
list_filename[3] = 'b'
list_filename[4] = mode | $30
list_filename[5] = 0
send_command(list_filename)
return status_code()==0
}
sub directory() -> bool {
; -- Prints the directory contents to the screen. Returns success.
@ -70,7 +83,8 @@ diskio {
void cbm.CHRIN() ; skip 2 bytes
void cbm.CHRIN()
status = cbm.READST()
if cbm.STOP2()
void cbm.STOP()
if_z
break
}
status = cbm.READST()
@ -319,7 +333,7 @@ close_end:
readsize = 255
if num_bytes<readsize
readsize = num_bytes
readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
void, readsize = cx16.MACPTR(lsb(readsize), bufferpointer, false) ; fast block reads
if_cs
goto byte_read_loop ; MACPTR block read not supported, do fallback loop
list_blocks += readsize
@ -456,7 +470,7 @@ _end rts
if num_bytes!=0 {
reset_write_channel()
do {
cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
void, cx16.r0 = cx16.MCIOUT(lsb(num_bytes), bufferpointer, false) ; fast block writes
if_cs
goto no_mciout
num_bytes -= cx16.r0
@ -525,6 +539,39 @@ io_error:
goto done
}
; similar to above, but instead of fetching the entire string, it only fetches the status code and returns it as ubyte
; in case of IO error, returns 255 (CMDR-DOS itself is physically unable to return such a value)
sub status_code() -> ubyte {
if cbm.READST()==128 {
return 255
}
cbm.SETNAM(0, list_filename)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN() ; open 15,8,15
if_cs
goto io_error
void cbm.CHKIN(15) ; use #15 as input channel
list_filename[0] = cbm.CHRIN()
list_filename[1] = cbm.CHRIN()
list_filename[2] = 0
while cbm.READST()==0 {
void cbm.CHRIN()
}
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(15)
return conv.str2ubyte(list_filename)
io_error:
cbm.CLRCHN()
cbm.CLOSE(15)
return 255
}
; saves a block of memory to disk, including the default 2 byte prg header.
sub save(uword filenameptr, uword startaddress, uword savesize) -> bool {
@ -641,7 +688,7 @@ io_error:
}
sub send_command(uword commandptr) {
; -- send a dos command to the drive
; -- send a dos command to the drive (don't read any response)
cbm.SETNAM(string.length(commandptr), commandptr)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
@ -822,7 +869,6 @@ io_error:
command[3] = msb(pos_loword)
command[4] = lsb(pos_hiword)
command[5] = msb(pos_hiword)
send_command:
cbm.SETNAM(sizeof(command), &command)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
@ -845,4 +891,55 @@ io_error:
reset_write_channel() ; back to the write io channel
}
asmsub f_tell() -> uword @R0, uword @R1, uword @R2, uword @R3 {
; -- Returns the current read position of the opened read file,
; in R0 and R1 (low + high words) and the file size in R2 and R3 (low + high words).
; Returns 0 as size if the command is not supported by the DOS implementation/version.
%asm {{
jmp internal_f_tell
}}
}
sub internal_f_tell() {
; gets the (32 bits) position + file size of the opened read file channel
ubyte[2] command = ['t',0]
command[1] = READ_IO_CHANNEL ; f_open uses this secondary address
cbm.SETNAM(sizeof(command), &command)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN()
void cbm.CHKIN(15) ; use #15 as input channel
bool success=false
; valid response starts with "07," followed by hex notations of the position and filesize
if cbm.CHRIN()=='0' and cbm.CHRIN()=='7' and cbm.CHRIN()==',' {
cx16.r1 = read4hex()
cx16.r0 = read4hex() ; position in R1:R0
void cbm.CHRIN() ; separator space
cx16.r3 = read4hex()
cx16.r2 = read4hex() ; filesize in R3:R2
success = true
}
while cbm.READST()==0 {
cx16.r5L = cbm.CHRIN()
if cx16.r5L=='\r' or cx16.r5L=='\n'
break
}
cbm.CLOSE(15)
reset_read_channel() ; back to the read io channel
if success
return
cx16.r0 = cx16.r1 = cx16.r2 = cx16.r3 = 0
sub read4hex() -> uword {
str hex = "0000"
hex[0] = cbm.CHRIN()
hex[1] = cbm.CHRIN()
hex[2] = cbm.CHRIN()
hex[3] = cbm.CHRIN()
return conv.hex2uword(hex)
}
}
}

View File

@ -1,5 +1,5 @@
; Emulator debug interface.
; Docs: https://github.com/X16Community/x16-emulator#emulator-io-registers
; Docs: https://github.com/X16Community/x16-emulator/tree/d52f118ce893fa24c4ca021a0b8de46cb80e5ccf#emulator-io-registers
emudbg {
%option ignore_unused
@ -41,7 +41,7 @@ emudbg {
chr = @(isoString)
if_z
break
EMU_CPUCLK_U = chr
EMU_CHROUT = chr
isoString++
}
}

View File

@ -4,12 +4,7 @@
%import shared_floats_functions
floats {
; ---- this block contains C-64 compatible floating point related functions ----
const float π = 3.141592653589793
const float PI = π
const float TWOPI = 2*π
; ---- this block contains C-64 compatible floating point related functions ----
; ---- ROM float functions (same as on C128 except base page) ----
@ -19,7 +14,7 @@ floats {
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
romsub $fe00 = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 'facmo' and 'faclo', MSB FIRST. (might throw ILLEGAL QUANTITY) See "basic.sym" kernal symbol file for their memory locations.
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; there is also floats.GIVUAYFAY - unsigned word in A/Y (lo/hi) to fac1
@ -43,7 +38,7 @@ romsub $fe21 = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $fe24 = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1
romsub $fe27 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 mind the order of the operands
romsub $fe2a = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $fe2d = INT() clobbers(A,X,Y) ; INT() truncates, use ROUND or FADDH first to round instead of trunc
romsub $fe2d = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to integer round instead of trunc
romsub $fe30 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $fe33 = NEGOP() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1)
romsub $fe36 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** float in A/Y
@ -53,7 +48,7 @@ romsub $fe3f = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $fe42 = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $fe45 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $fe48 = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
romsub $fe4b = ROUND() clobbers(A,X,Y) ; round fac1
romsub $fe4b = ROUND() clobbers(A,X,Y) ; round least significant bit of fac1
romsub $fe4e = ABS() clobbers(A,X,Y) ; fac1 = ABS(fac1)
romsub $fe51 = SIGN() clobbers(X,Y) -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $fe54 = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
@ -65,10 +60,10 @@ romsub $fe60 = MOVFRM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value f
romsub $fe63 = MOVFM(uword mflpt @ AY) clobbers(A,X,Y) ; load mflpt value from memory in A/Y into fac1
romsub $fe66 = MOVMF(uword mflpt @ XY) clobbers(A,X,Y) ; store fac1 to memory X/Y as 5-byte mflpt
romsub $fe69 = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $fe6c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded the least significant bit)
; X16 additions
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $fe6f = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for integer rounding- call this before INT
romsub $fe72 = ZEROFC() clobbers(A,X,Y) ; fac1 = 0
romsub $fe75 = NORMAL() clobbers(A,X,Y) ; normalize fac1
romsub $fe78 = NEGFAC() clobbers(A) ; switch the sign of fac1 (fac1 = -fac1) (doesn't work, juse use NEGOP() instead!)
@ -98,8 +93,8 @@ asmsub FREADSA (byte value @A) clobbers(A,X,Y) {
asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
sty $c4 ; facmo ($64 on c128)
sta $c5 ; facmo+1 ($65 on c128)
sty $c4 ; facmo
sta $c5 ; facmo+1
ldx #$90
sec
jmp FLOATC
@ -136,14 +131,12 @@ asmsub FREADUY (ubyte value @Y) {
}
asmsub parse(str value @AY) -> float @FAC1 {
; -- parse a string value of a number to float in FAC1
; warning: on older <R47 kernals it uses an internal BASIC routine that is ROM version dependent,
; ($deb6 is inside the routine for VAL at $deb3) See basic.sym from x16-rom
; TODO once ROM v47 is released, all the workarounds here can be removed. But probably keep the kernal VAL_1 existance check
; -- Parse a string value of a number to float in FAC1.
; Requires kernal R47 or newer! (depends on val_1 working)
%asm {{
ldx VAL_1
cpx #$4c ; is there an implementation in VAL_1? (test for JMP)
bne + ; no, do it ourselves
bne _borked ; no, print error message
pha ; yes, count the length and call rom VAL_1.
phy
jsr prog8_lib.strlen
@ -151,15 +144,7 @@ asmsub parse(str value @AY) -> float @FAC1 {
ply
plx
jmp VAL_1
+ sta $a9 ; 'index' variable
sty $aa
jsr prog8_lib.strlen
lda $deb6
cmp #$d0 ; sanity check for kernal routine correct
bne +
tya
jmp $deb6 ; kernal version dependent...
+ ; print error message if routine is borked in kernal, and exit program
_borked
ldy #0
- lda _msg,y
beq +
@ -168,12 +153,12 @@ asmsub parse(str value @AY) -> float @FAC1 {
bne -
+ jmp sys.exit
_msg .text 13,"?val kaputt",13,0
_msg .text 13,"?rom 47+ required for val1",13,0
}}
}
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT
&uword AYINT_facmo = $c6 ; $c6/$c7 contain result of AYINT (See "basic.sym" kernal symbol file)
sub rnd() -> float {
%asm {{

View File

@ -48,7 +48,7 @@ psg {
sub freq(ubyte voice_num, uword vera_freq) {
; -- Changes the frequency of the voice's sound.
; voice_num = 0-15, vera_freq = 0-65535 calculate this via the formula given in the Vera's PSG documentation.
; (https://github.com/x16community/x16-docs/blob/master/VERA%20Programmer's%20Reference.md)
; (https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2009%20-%20VERA%20Programmer's%20Reference.md)
; Write freq MSB first and then LSB to reduce the chance on clicks
sys.irqsafe_set_irqd()
cx16.r0 = $f9c1 + voice_num * 4

View File

@ -5,6 +5,7 @@
; note: sprites z-order will be in front of all layers.
; note: collision mask is not supported here yet.
; note: "palette offset" is counted as 0-15 (vera multiplies the offset by 16 to get at the actual color index)
sprites {
%option ignore_unused
@ -21,7 +22,7 @@ sprites {
sub init(ubyte spritenum,
ubyte databank, uword dataaddr,
ubyte width_flag, ubyte height_flag,
ubyte colors_flag, ubyte palette_offset_idx) {
ubyte colors_flag, ubyte palette_offset) {
hide(spritenum)
cx16.VERA_DC_VIDEO |= %01000000 ; enable sprites globally
dataaddr >>= 5
@ -30,7 +31,7 @@ sprites {
cx16.vpoke(1, sprite_reg, lsb(dataaddr)) ; address 12:5
cx16.vpoke(1, sprite_reg+1, colors_flag | msb(dataaddr)) ; 4 bpp + address 16:13
cx16.vpoke(1, sprite_reg+6, %00001100) ; z depth %11 = in front of both layers, no flips
cx16.vpoke(1, sprite_reg+7, height_flag<<6 | width_flag<<4 | palette_offset_idx>>4) ; 64x64 pixels, palette offset
cx16.vpoke(1, sprite_reg+7, height_flag<<6 | width_flag<<4 | palette_offset&15) ; 64x64 pixels, palette offset
}
sub data(ubyte spritenum, ubyte bank, uword addr) {
@ -143,7 +144,7 @@ sprites {
}
sub set_palette_offset(ubyte spritenum, ubyte offset) {
cx16.vpoke_mask(1, VERA_SPRITEREGS + 7 + spritenum*$0008, %11110000, offset>>4)
cx16.vpoke_mask(1, VERA_SPRITEREGS + 7 + spritenum*$0008, %11110000, offset&15)
}
sub set_mousepointer_hand() {

View File

@ -19,7 +19,7 @@ romsub $FF8D = VECTOR(uword userptr @ XY, bool dir @ Pc) clobbers(A,Y) ; rea
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, bool 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 cx16.numbanks()
romsub $FF99 = MEMTOP(uword address @ XY, bool dir @ Pc) -> uword @ XY, ubyte @A ; read/set top of memory pointer. NOTE: on the Cx16 also returns the number of RAM memory banks in A! Also see cx16.numbanks()
romsub $FF9C = MEMBOT(uword address @ XY, bool dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard, also called kbd_scan
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
@ -32,7 +32,7 @@ romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word (use CLEARST to reset it to 0)
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC0 = OPEN() clobbers(X,Y) -> bool @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) -> bool @Pc ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
@ -42,37 +42,50 @@ romsub $FFD2 = CHROUT(ubyte character @ A) ; (via 806
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> bool @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; (via 818 ($332)) save to a device. See also BSAVE
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (A=lo,X=mid,Y=high)
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock (in little endian order: A=lo,X=mid,Y=high) , however use RDTIM_safe() instead
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A) also see STOP2
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; (via 810 ($32A)) get a character also see GETIN2
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use txt.plot for a 'safe' wrapper that preserves X.
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; get size of text screen into X (columns) and Y (rows)
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, bool dir @ Pc) clobbers(A) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Also see txt.plot
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- utility
asmsub STOP2() clobbers(X) -> bool @A {
; -- check if STOP key was pressed, returns true if so. More convenient to use than STOP() because that only sets the zero status flag.
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
; -- just like STOP, but omits the special keys result value in A.
; just for convenience because most of the times you're only interested in the stop pressed or not status.
%asm {{
jsr cbm.STOP
beq +
lda #0
rts
+ lda #1
rts
}}
}
asmsub RDTIM16() clobbers(X) -> uword @AY {
; -- like RDTIM() but only returning the lower 16 bits in AY for convenience. Also avoids ram bank issue for irqs.
inline asmsub GETIN2() clobbers(X,Y) -> ubyte @A {
; -- just like GETIN, but omits the carry flag result value.
; just for convenience because GETIN is so often used to just read keyboard input,
; where you don't have to deal with a potential error status
%asm {{
jsr cbm.GETIN
}}
}
asmsub RDTIM_safe() -> ubyte @ A, ubyte @ X, ubyte @ Y {
; -- read the software clock (in little endian order: A=lo,X=mid,Y=high)
; with safeguard for ram bank issue for irqs.
%asm {{
php
sei
jsr cbm.RDTIM
plp
cli
rts
}}
}
asmsub RDTIM16() clobbers(X) -> uword @AY {
; -- like RDTIM_safe() but only returning the lower 16 bits in AY for convenience.
%asm {{
jsr RDTIM_safe
pha
txa
tay
@ -295,33 +308,33 @@ cx16 {
&ubyte VERA_SPI_CTRL = VERA_BASE + $001F
; Vera FX registers: (accessing depends on particular DCSEL value set in VERA_CTRL!)
&ubyte VERA_FX_CTRL = VERA_BASE + $0009
&ubyte VERA_FX_TILEBASE = VERA_BASE + $000a
&ubyte VERA_FX_MAPBASE = VERA_BASE + $000b
&ubyte VERA_FX_MULT = VERA_BASE + $000c
&ubyte VERA_FX_X_INCR_L = VERA_BASE + $0009
&ubyte VERA_FX_X_INCR_H = VERA_BASE + $000a
&uword VERA_FX_X_INCR = VERA_BASE + $0009
&ubyte VERA_FX_Y_INCR_L = VERA_BASE + $000b
&ubyte VERA_FX_Y_INCR_H = VERA_BASE + $000c
&uword VERA_FX_Y_INCR = VERA_BASE + $000b
&ubyte VERA_FX_X_POS_L = VERA_BASE + $0009
&ubyte VERA_FX_X_POS_H = VERA_BASE + $000a
&uword VERA_FX_X_POS = VERA_BASE + $0009
&ubyte VERA_FX_Y_POS_L = VERA_BASE + $000b
&ubyte VERA_FX_Y_POS_H = VERA_BASE + $000c
&uword VERA_FX_Y_POS = VERA_BASE + $000b
&ubyte VERA_FX_X_POS_S = VERA_BASE + $0009
&ubyte VERA_FX_Y_POS_S = VERA_BASE + $000a
&ubyte VERA_FX_POLY_FILL_L = VERA_BASE + $000b
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000c
&uword VERA_FX_POLY_FILL = VERA_BASE + $000b
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000a
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000b
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000c
&ubyte VERA_FX_ACCUM = VERA_BASE + $000a
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009
&ubyte VERA_FX_CTRL = VERA_BASE + $0009
&ubyte VERA_FX_TILEBASE = VERA_BASE + $000a
&ubyte VERA_FX_MAPBASE = VERA_BASE + $000b
&ubyte VERA_FX_MULT = VERA_BASE + $000c
&ubyte VERA_FX_X_INCR_L = VERA_BASE + $0009
&ubyte VERA_FX_X_INCR_H = VERA_BASE + $000a
&uword VERA_FX_X_INCR = VERA_BASE + $0009
&ubyte VERA_FX_Y_INCR_L = VERA_BASE + $000b
&ubyte VERA_FX_Y_INCR_H = VERA_BASE + $000c
&uword VERA_FX_Y_INCR = VERA_BASE + $000b
&ubyte VERA_FX_X_POS_L = VERA_BASE + $0009
&ubyte VERA_FX_X_POS_H = VERA_BASE + $000a
&uword VERA_FX_X_POS = VERA_BASE + $0009
&ubyte VERA_FX_Y_POS_L = VERA_BASE + $000b
&ubyte VERA_FX_Y_POS_H = VERA_BASE + $000c
&uword VERA_FX_Y_POS = VERA_BASE + $000b
&ubyte VERA_FX_X_POS_S = VERA_BASE + $0009
&ubyte VERA_FX_Y_POS_S = VERA_BASE + $000a
&ubyte VERA_FX_POLY_FILL_L = VERA_BASE + $000b
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000c
&uword VERA_FX_POLY_FILL = VERA_BASE + $000b
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000a
&ubyte VERA_FX_CACHE_H = VERA_BASE + $000b
&ubyte VERA_FX_CACHE_U = VERA_BASE + $000c
&ubyte VERA_FX_ACCUM = VERA_BASE + $000a
&ubyte VERA_FX_ACCUM_RESET = VERA_BASE + $0009
; VERA_PSG_BASE = $1F9C0
@ -376,20 +389,16 @@ cx16 {
; ---- Commander X-16 additions on top of C64 kernal routines ----
; spelling of the names is taken from the Commander X-16 rom sources
; supported C128 additions
romsub $ff4a = CLOSE_ALL(ubyte device @A) clobbers(A,X,Y)
romsub $ff59 = LKUPLA(ubyte la @A) clobbers(A,X,Y)
romsub $ff5c = LKUPSA(ubyte sa @Y) clobbers(A,X,Y)
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) clobbers(X, Y) -> ubyte @A, bool @Pc ; note: X,Y size result is not supported, use SCREEN or get_screen_mode routine for that
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y) ; incompatible with C128 dlchr()
; not yet supported: romsub $ff65 = pfkey() clobbers(A,X,Y)
romsub $ff5f = screen_mode(ubyte mode @A, bool getCurrent @Pc) -> ubyte @A, ubyte @X, ubyte @Y, bool @Pc ; also see SCREEN or get_screen_mode()
romsub $ff62 = screen_set_charset(ubyte charset @A, uword charsetptr @XY) clobbers(A,X,Y)
romsub $ff6e = JSRFAR() ; following word = address to call, byte after that=rom/ram bank it is in
romsub $ff74 = fetch(ubyte bank @X, ubyte index @Y) clobbers(X) -> ubyte @A
romsub $ff77 = stash(ubyte data @A, ubyte bank @X, ubyte index @Y) clobbers(X)
romsub $ff7d = PRIMM()
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
; high level graphics & fonts
romsub $ff20 = GRAPH_init(uword vectors @R0) clobbers(A,X,Y)
romsub $ff23 = GRAPH_clear() clobbers(A,X,Y)
@ -422,9 +431,11 @@ romsub $ff1a = FB_filter_pixels(uword pointer @ R0, uword count @R1) clobbers(A
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 $FEBA = BSAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; like cbm.SAVE, but omits the 2-byte prg header
romsub $fec6 = i2c_read_byte(ubyte device @X, ubyte offset @Y) clobbers (X,Y) -> ubyte @A, bool @Pc
romsub $fec9 = i2c_write_byte(ubyte device @X, ubyte offset @Y, ubyte data @A) clobbers (A,X,Y) -> bool @Pc
romsub $feb4 = i2c_batch_read(ubyte device @X, uword buffer @R0, uword length @R1, bool advance @Pc) clobbers(A,Y) -> bool @Pc
romsub $feb7 = i2c_batch_write(ubyte device @X, uword buffer @R0, uword length @R1, bool advance @Pc) clobbers(A,Y) -> bool @Pc
romsub $fef0 = sprite_set_image(uword pixels @R0, uword mask @R1, ubyte bpp @R2, ubyte number @A, ubyte width @X, ubyte height @Y, bool apply_mask @Pc) clobbers(A,X,Y) -> bool @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)
@ -437,10 +448,13 @@ romsub $fee1 = console_get_char() clobbers(X,Y) -> ubyte @A
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 $fecf = entropy_get() -> ubyte @A, ubyte @X, ubyte @Y
;; romsub $fea8 = extapi16(ubyte callnumber @A) clobbers (A,X,Y) ; not useful yet because is for 65816 cpu
romsub $feab = extapi(ubyte callnumber @A) clobbers (A,X,Y)
romsub $fecc = monitor() clobbers(A,X,Y)
romsub $ff44 = MACPTR(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $feb1 = MCIOUT(ubyte length @A, uword buffer @XY, bool dontAdvance @Pc) clobbers(A) -> bool @Pc, uword @XY
romsub $FEBA = BSAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) clobbers (X, Y) -> bool @ Pc, ubyte @ A ; like cbm.SAVE, but omits the 2-byte prg header
romsub $ff47 = enter_basic(bool cold_or_warm @Pc) clobbers(A,X,Y)
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, uword jiffiesweekday @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, uword @R3 ; result registers see clock_set_date_time()
@ -448,16 +462,14 @@ romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1
; keyboard, mouse, joystick
; note: also see the cbm.kbdbuf_clear() helper routine
romsub $febd = kbdbuf_peek() -> ubyte @A, ubyte @X ; key in A, queue length in X
romsub $febd = kbdbuf_peek2() -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
romsub $fec0 = kbdbuf_get_modifiers() -> ubyte @A
romsub $fec3 = kbdbuf_put(ubyte key @A) clobbers(X)
romsub $fed2 = keymap(uword identifier @XY, bool read @Pc) -> bool @Pc
romsub $ff68 = mouse_config(byte shape @A, ubyte resX @X, ubyte resY @Y) clobbers (A, X, Y)
romsub $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A
romsub $ff6b = mouse_get(ubyte zdataptr @X) -> ubyte @A, byte @X ; use mouse_pos() instead
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 $ff56 = joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX ; alternative to above to not have the hassle to deal with multiple return values
romsub $ff56 = joystick_get(ubyte joynr @A) -> uword @AX, bool @Y ; note: everything is inverted
; X16Edit (rom bank 13/14 but you ideally should use the routine search_x16edit() to search for the correct bank)
romsub $C000 = x16edit_default() clobbers(A,X,Y)
@ -524,6 +536,28 @@ romsub $C099 = ym_getatten(ubyte channel @A) clobbers(Y) -> ubyte @X
romsub $C09C = ym_getpan(ubyte channel @A) clobbers(Y) -> ubyte @X
romsub $C0A5 = ym_get_chip_type() clobbers(X) -> ubyte @A
; extapi call numbers
const ubyte EXTAPI_clear_status = $01
const ubyte EXTAPI_getlfs = $02
const ubyte EXTAPI_mouse_sprite_offset = $03
const ubyte EXTAPI_joystick_ps2_keycodes = $04
const ubyte EXTAPI_iso_cursor_char = $05
const ubyte EXTAPI_ps2kbd_typematic = $06
const ubyte EXTAPI_pfkey = $07
const ubyte EXTAPI_ps2data_fetch = $08
const ubyte EXTAPI_ps2data_raw = $09
const ubyte EXTAPI_cursor_blink = $0A
const ubyte EXTAPI_led_update = $0B
const ubyte EXTAPI_mouse_set_position = $0C
const ubyte EXTAPI_scnsiz = $0D ; rom R48+
; extapi16 call numbers
const ubyte EXTAPI16_test = $00
const ubyte EXTAPI16_stack_push = $01
const ubyte EXTAPI16_stack_pop = $02
const ubyte EXTAPI16_stack_enter_kernal_stack = $03
const ubyte EXTAPI16_stack_leave_kernal_stack = $04
asmsub set_screen_mode(ubyte mode @A) clobbers(A,X,Y) -> bool @Pc {
; -- convenience wrapper for screen_mode() to just set a new mode (and return success)
@ -533,9 +567,8 @@ asmsub set_screen_mode(ubyte mode @A) clobbers(A,X,Y) -> bool @Pc {
}}
}
asmsub get_screen_mode() -> byte @A, byte @X, byte @Y {
; -- convenience wrapper for screen_mode() to just get the current mode in A, and size in characters in X+Y
; this does need a piece of inlined asm to call it ans store the result values if you call this from prog8 code
asmsub get_screen_mode() -> ubyte @A, ubyte @X, ubyte @Y {
; -- convenience wrapper for screen_mode() to just get the current mode in A, and size in characters in X (width) and Y (height)
; Note: you can also just do the SEC yourself and simply call screen_mode() directly,
; or use the existing SCREEN kernal routine for just getting the size in characters.
%asm {{
@ -555,15 +588,72 @@ asmsub mouse_config2(byte shape @A) clobbers (A, X, Y) {
}}
}
asmsub mouse_pos() clobbers(X) -> ubyte @A {
asmsub mouse_pos() -> ubyte @A, uword @R0, uword @R1, byte @X {
; -- short wrapper around mouse_get() kernal routine:
; -- gets the position of the mouse cursor in cx16.r0 and cx16.r1 (x/y coordinate), returns mouse button status in A.
; -- gets the position of the mouse cursor in cx16.r0 and cx16.r1 (x/y coordinate), returns mouse button status in A, scroll wheel in X.
; Note: mouse pointer needs to be enabled for this to do anything.
%asm {{
ldx #cx16.r0
jmp cx16.mouse_get
}}
}
; shims for the kernal routines called via the extapi call:
asmsub mouse_set_pos(uword xpos @R0, uword ypos @R1) clobbers(X) {
; -- sets the mouse sprite position
; Note: mouse pointer needs to be enabled for this to do anything.
%asm {{
ldx #cx16.r0L
lda #EXTAPI_mouse_set_position
jmp cx16.extapi
}}
}
asmsub mouse_set_sprite_offset(word xoffset @R0, word yoffset @R1) clobbers(A,X,Y) {
%asm {{
clc
lda #EXTAPI_mouse_sprite_offset
jmp cx16.extapi
}}
}
asmsub mouse_get_sprite_offset() clobbers(A,X,Y) -> word @R0, word @R1 {
%asm {{
sec
lda #EXTAPI_mouse_sprite_offset
jmp cx16.extapi
}}
}
asmsub getlfs() -> ubyte @X, ubyte @A, ubyte @Y {
; -- return the result of the last call to SETLFS: A=logical, X=device, Y=secondary.
%asm {{
lda #EXTAPI_mouse_set_position
jmp cx16.extapi
}}
}
asmsub iso_cursor_char(ubyte character @X) clobbers(A,X,Y) {
; -- set the screen code for the cursor character in ISO mode (the default is $9f).
%asm {{
clc
lda #EXTAPI_iso_cursor_char
jmp cx16.extapi
}}
}
asmsub scnsiz(ubyte width @X, ubyte heigth @Y) clobbers(A,X,Y) {
; -- sets the screen editor size dimensions (without changing the graphical screen mode itself)
; (rom R48+)
%asm {{
lda #EXTAPI_scnsiz
jmp cx16.extapi
}}
}
; TODO : implement shims for the remaining extapi calls.
; ---- end of kernal routines ----
@ -918,7 +1008,7 @@ asmsub restore_vera_context() clobbers(A) {
asmsub set_chrin_keyhandler(ubyte handlerbank @A, uword handler @XY) clobbers(A) {
; Install a custom CHRIN (BASIN) key handler in a safe manner. Call this before each line you want to read.
; See https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2002%20-%20Editor.md#custom-basin-petscii-code-override-handler
; See https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2003%20-%20Editor.md#custom-basin-petscii-code-override-handler
%asm {{
sei
sta P8ZP_SCRATCH_REG
@ -1158,7 +1248,7 @@ sub search_x16edit() -> ubyte {
}
asmsub cpu_is_65816() -> bool @A {
; Returns true when you have a 65816 cpu, false when it's a 6502.
; -- Returns true when you have a 65816 cpu, false when it's a 6502.
%asm {{
php
clv
@ -1174,9 +1264,9 @@ sub search_x16edit() -> ubyte {
}
sub set_program_args(uword args_ptr, ubyte args_size) {
; Set the inter-program arguments.
; -- Set the inter-program arguments.
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
; see https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2007%20-%20Memory%20Map.md#bank-0
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
sys.push(getrambank())
rambank(0)
sys.memcopy(args_ptr, $bf00, args_size)
@ -1186,9 +1276,9 @@ sub search_x16edit() -> ubyte {
}
asmsub get_program_args(uword buffer @R0, ubyte buf_size @R1, bool binary @Pc) {
; Retrieve the inter-program arguments. If binary=false, it treats them as a string (stops copying at first zero).
; -- Retrieve the inter-program arguments. If binary=false, it treats them as a string (stops copying at first zero).
; standardized way to pass arguments between programs is in ram bank 0, address $bf00-$bfff.
; see https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2007%20-%20Memory%20Map.md#bank-0
; see https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2008%20-%20Memory%20Map.md#bank-0
%asm {{
lda #0
rol a
@ -1215,19 +1305,40 @@ _continue iny
}
sub reset_system() {
; Soft-reset the system back to initial power-on Basic prompt.
; -- Soft-reset the system back to initial power-on Basic prompt.
sys.reset_system()
}
sub poweroff_system() {
; use the SMC to shutdown the computer
; -- use the SMC to shutdown the computer
void cx16.i2c_write_byte($42, $01, $00)
}
sub set_led_brightness(ubyte brightness) {
void cx16.i2c_write_byte($42, $05, brightness)
sub set_led_state(bool on) {
; -- sets the computer's activity led on/off
cx16.r0L = 0
if on
cx16.r0 = 255
void cx16.i2c_write_byte($42, $05, cx16.r0L)
}
asmsub rom_version() clobbers(Y) -> ubyte @A, bool @Pc {
; Returns the KERNEL ROM version. Carry set if pre-release, clear if offical release.
%asm{{
; the ROM BANK is unknown on entry
ldy $01
stz $01 ; KERNEL ROM
clc ; prepare for released ROM
lda $FF80
bpl _final ; pre-release versions are negative
eor #$FF ; twos complement
ina
sec
_final:
sty $01
rts
}}
}
}
sys {
@ -1442,11 +1553,6 @@ asmsub set_rasterline(uword line @AY) {
void cx16.i2c_write_byte($42, $01, $00)
}
sub set_leds_brightness(ubyte activity, ubyte power) {
void cx16.i2c_write_byte($42, $04, power)
void cx16.i2c_write_byte($42, $05, activity)
}
asmsub wait(uword jiffies @AY) clobbers(X) {
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock

View File

@ -4,7 +4,7 @@
%import syslib
%import conv
%import shared_textio_functions
%import shared_cbm_textio_functions
txt {
@ -16,11 +16,17 @@ const ubyte DEFAULT_HEIGHT = 60
const ubyte VERA_TEXTMATRIX_BANK = 1
const uword VERA_TEXTMATRIX_ADDR = $b000
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
@ -223,8 +229,8 @@ sub uppercase() {
}
sub iso() {
; -- switch to iso-8859-15 character set
cbm.CHROUT($0f)
; This doesn't enable it completely: cx16.screen_set_charset(1, 0) ; iso charset
}
sub iso_off() {
@ -232,6 +238,29 @@ sub iso_off() {
cbm.CHROUT($8f)
}
sub cp437() {
; -- switch to CP-437 (ibm PC) character set
cbm.CHROUT($0f) ; iso mode
cx16.screen_set_charset(7, 0) ; charset
%asm {{
clc
ldx #95 ; underscore
lda #cx16.EXTAPI_iso_cursor_char
jsr cx16.extapi
}}
}
sub iso5() {
; -- switch to iso-8859-5 character set (Cyrillic)
cbm.CHROUT($0f) ; iso mode
cx16.screen_set_charset(8, 0) ; charset
}
sub iso16() {
; -- switch to iso-8859-16 character set (Eastern Europe)
cbm.CHROUT($0f) ; iso mode
cx16.screen_set_charset(10, 0) ; charset
}
asmsub scroll_left() clobbers(A, X, Y) {
; ---- scroll the whole screen 1 character to the left
@ -431,215 +460,6 @@ _nextline
}}
}
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string, in PETSCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
pha
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
txa
jmp cbm.CHROUT
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
bra _ones
+ pla
cmp #'0'
beq _ones
jsr cbm.CHROUT
_ones txa
jmp cbm.CHROUT
}}
}
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
pha
cmp #0
bpl +
lda #'-'
jsr cbm.CHROUT
+ pla
jsr conv.byte2decimal
bra print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
bcc +
pha
lda #'$'
jsr cbm.CHROUT
pla
+ jsr conv.ubyte2hex
jsr cbm.CHROUT
tya
jmp cbm.CHROUT
}}
}
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr cbm.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr cbm.CHROUT
dey
bne -
rts
}}
}
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
bra print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
bra print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr cbm.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp cbm.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr cbm.CHROUT
tya
eor #255
tay
pla
eor #255
ina
bne +
iny
+ bra print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr cbm.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
; ---- sets the character in the screen matrix at the given position
%asm {{
@ -811,4 +631,35 @@ asmsub waitkey() -> ubyte @A {
rts
}}
}
asmsub chrout_lit(ubyte character @A) {
; -- print the character always as a literal character, not as possible control code.
%asm {{
tax
lda #128
jsr cbm.CHROUT
txa
jmp cbm.CHROUT
}}
}
asmsub print_lit(str text @ AY) clobbers(A,Y) {
; -- print zero terminated string, from A/Y, as all literal characters (no control codes)
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
- lda (P8ZP_SCRATCH_W2),y
beq +
tax
lda #128
jsr cbm.CHROUT
txa
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
}

View File

@ -1,6 +1,6 @@
; Somewhat experimental Vera FX support.
; Docs:
; https://github.com/X16Community/x16-docs/blob/master/X16%20Reference%20-%2010%20-%20VERA%20FX%20Reference.md
; https://github.com/X16Community/x16-docs/blob/101759f3bfa5e6cce4e8c5a0b67cb0f2f1c6341e/X16%20Reference%20-%2010%20-%20VERA%20FX%20Reference.md
; https://docs.google.com/document/d/1q34uWOiM3Be2pnaHRVgSdHySI-qsiQWPTo_gfE54PTg
verafx {
@ -111,17 +111,15 @@ verafx {
; unsigned multiplication just passes the values as signed to muls
; if you do this yourself in your call to muls, it will save a few instructions.
sub mult(uword value1, uword value2) -> uword {
; Returns the lower 16 bits of the 32 bits result,
; the upper 16 bits are stored in cx16.r0 so you can access those separately.
; It's not part of the subroutine's signature to avoid awkward use of multiple returnvalues.
return muls(value1 as word, value2 as word) as uword
inline asmsub mult(uword value1 @R0, uword value2 @R1) clobbers(X) -> uword @AY, uword @R0 {
; Returns the 32 bits unsigned result in AY and R0 (lower word, upper word).
%asm {{
jsr verafx.muls
}}
}
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY {
; Returns the lower 16 bits of the 32 bits result in AY,
; the upper 16 bits are stored in cx16.r0 so you can access those separately.
; It's not part of the subroutine's signature to avoid awkward use of multiple returnvalues.
asmsub muls(word value1 @R0, word value2 @R1) clobbers(X) -> word @AY, word @R0 {
; Returns the 32 bits signed result in AY and R0 (lower word, upper word).
%asm {{
lda #(2 << 1)
sta cx16.VERA_CTRL ; $9F25
@ -131,13 +129,13 @@ verafx {
lda #(6 << 1)
sta cx16.VERA_CTRL ; $9F25
lda cx16.r0
ldy cx16.r0+1
sta cx16.VERA_FX_CACHE_L ; $9F29
sty cx16.VERA_FX_CACHE_M ; $9F2A
lda cx16.r0+1
sta cx16.VERA_FX_CACHE_M ; $9F2A
lda cx16.r1
ldy cx16.r1+1
sta cx16.VERA_FX_CACHE_H ; $9F2B
sty cx16.VERA_FX_CACHE_U ; $9F2C
lda cx16.r1+1
sta cx16.VERA_FX_CACHE_U ; $9F2C
lda cx16.VERA_FX_ACCUM_RESET ; $9F29 (DCSEL=6)
; Set the ADDR0 pointer to $1f9bc and write our multiplication result there
@ -155,12 +153,12 @@ verafx {
stz cx16.VERA_DATA0 ; multiply and write out result
lda #%00010001 ; $01 with Increment 1
sta cx16.VERA_ADDR_H ; so we can read out the result
lda cx16.VERA_DATA0
lda cx16.VERA_DATA0 ; store the lower 16 bits of the result in AY
ldy cx16.VERA_DATA0
ldx cx16.VERA_DATA0 ; store the upper 16 bits of the result in r0
stx cx16.r0
ldx cx16.VERA_DATA0 ; store the upper 16 bits of the result in R0
stx cx16.r0s
ldx cx16.VERA_DATA0
stx cx16.r0+1
stx cx16.r0s+1
stz cx16.VERA_FX_CTRL ; Cache write disable
stz cx16.VERA_CTRL ; reset DCSEL
rts

View File

@ -59,7 +59,8 @@ diskio {
void cbm.CHRIN() ; skip 2 bytes
void cbm.CHRIN()
status = cbm.READST()
if cbm.STOP2()
void cbm.STOP()
if_z
break
}
status = cbm.READST()
@ -460,6 +461,38 @@ io_error:
goto done
}
; similar to above, but instead of fetching the entire string, it only fetches the status code and returns it as ubyte
; in case of IO error, returns 255 (CBM-DOS itself is physically unable to return such a value)
sub status_code() -> ubyte {
if cbm.READST()==128 {
return 255
}
cbm.SETNAM(0, list_filename)
cbm.SETLFS(15, drivenumber, 15)
void cbm.OPEN() ; open 15,8,15
if_cs
goto io_error
void cbm.CHKIN(15) ; use #15 as input channel
list_filename[0] = cbm.CHRIN()
list_filename[1] = cbm.CHRIN()
list_filename[2] = 0
while cbm.READST()==0 {
void cbm.CHRIN()
}
cbm.CLRCHN() ; restore default i/o devices
cbm.CLOSE(15)
return conv.str2ubyte(list_filename)
io_error:
cbm.CLRCHN()
cbm.CLOSE(15)
return 255
}
sub save(uword filenameptr, uword start_address, uword savesize) -> bool {
cbm.SETNAM(string.length(filenameptr), filenameptr)
cbm.SETLFS(1, drivenumber, 0)

View File

@ -29,20 +29,26 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; define an outp
romsub $FFCC = CLRCHN() clobbers(A,X) ; restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte character @ A) ; output a character
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; check the STOP key (and some others in A)
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; get a character
romsub $FFE1 = STOP() clobbers(X) -> bool @ Pz, ubyte @ A ; check the STOP key (and some others in A) also see STOP2
romsub $FFE4 = GETIN() clobbers(X,Y) -> bool @Pc, ubyte @ A ; get a character also see GETIN2
romsub $FFE7 = CLALL() clobbers(A,X) ; close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
asmsub STOP2() clobbers(X) -> bool @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.
inline asmsub STOP2() clobbers(X,A) -> bool @Pz {
; -- just like STOP, but omits the special keys result value in A.
; just for convenience because most of the times you're only interested in the stop pressed or not status.
%asm {{
jsr cbm.STOP
beq +
lda #0
rts
+ lda #1
rts
}}
}
inline asmsub GETIN2() clobbers(X,Y) -> ubyte @A {
; -- just like GETIN, but omits the carry flag result value.
; just for convenience because GETIN is so often used to just read keyboard input,
; where you don't have to deal with a potential error status
%asm {{
jsr cbm.GETIN
}}
}

View File

@ -4,7 +4,7 @@
%import syslib
%import conv
%import shared_textio_functions
%import shared_cbm_textio_functions
txt {
%option no_symbol_prefixing, ignore_unused
@ -12,11 +12,17 @@ txt {
const ubyte DEFAULT_WIDTH = 40
const ubyte DEFAULT_HEIGHT = 25
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
sub clear_screen() {
chrout(147)
}
sub cls() {
chrout(147)
}
sub home() {
chrout(19)
}
@ -145,216 +151,6 @@ asmsub scroll_down () clobbers(A,X) {
}}
}
romsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string, in PETSCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_B1
sty P8ZP_SCRATCH_REG
ldy #0
- lda (P8ZP_SCRATCH_B1),y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
jsr conv.ubyte2decimal
pha
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
txa
jmp cbm.CHROUT
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
jsr conv.ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr cbm.CHROUT
pla
jsr cbm.CHROUT
jmp _ones
+ pla
cmp #'0'
beq _ones
jsr cbm.CHROUT
_ones txa
jmp cbm.CHROUT
}}
}
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
pha
cmp #0
bpl +
lda #'-'
jsr cbm.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
}}
}
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
bcc +
pha
lda #'$'
jsr cbm.CHROUT
pla
+ jsr conv.ubyte2hex
jsr cbm.CHROUT
tya
jmp cbm.CHROUT
}}
}
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr cbm.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr cbm.CHROUT
dey
bne -
rts
}}
}
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit
jsr cbm.CHROUT
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp cbm.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr cbm.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr cbm.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A, Y) {
; ---- sets the character in the screen matrix at the given position
%asm {{

View File

@ -0,0 +1,251 @@
txt {
; the textio functions common across CBM-compatible compiler targets (c64, c128, pet32 and cx16)
%option merge, no_symbol_prefixing, ignore_unused
asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print zero terminated string, in PETSCII encoding, from A/Y
; note: the compiler contains an optimization that will replace
; a call to this subroutine with a string argument of just one char,
; by just one call to cbm.CHROUT of that single char.
%asm {{
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1
ldy #0
- lda (P8ZP_SCRATCH_W2),y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_ubhex (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
bcc +
pha
lda #'$'
jsr cbm.CHROUT
pla
+ jsr conv.internal_ubyte2hex
jsr cbm.CHROUT
tya
jmp cbm.CHROUT
}}
}
asmsub print_ubbin (ubyte value @ A, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
sta P8ZP_SCRATCH_B1
bcc +
lda #'%'
jsr cbm.CHROUT
+ ldy #8
- lda #'0'
asl P8ZP_SCRATCH_B1
bcc +
lda #'1'
+ jsr cbm.CHROUT
dey
bne -
rts
}}
}
asmsub print_uwbin (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{
pha
tya
jsr print_ubbin
pla
clc
jmp print_ubbin
}}
}
asmsub print_uwhex (uword value @ AY, bool prefix @ Pc) clobbers(A,X,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
%asm {{
pha
tya
jsr print_ubhex
pla
clc
jmp print_ubhex
}}
}
asmsub print_uw0 (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{
jsr conv.internal_uword2decimal
ldy #0
- lda conv.internal_uword2decimal.decTenThousands,y
beq +
jsr cbm.CHROUT
iny
bne -
+ rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,X,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
jsr conv.internal_uword2decimal
ldy #0
- lda conv.internal_uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit jsr cbm.CHROUT
iny
lda conv.internal_uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero lda #'0'
jmp cbm.CHROUT
}}
}
asmsub print_w (word value @ AY) clobbers(A,X,Y) {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{
cpy #0
bpl +
pha
lda #'-'
jsr cbm.CHROUT
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jmp print_uw
}}
}
asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
; ---- Input a string (max. 80 chars) from the keyboard, in PETSCII encoding.
; Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel!
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0 ; char counter = 0
- jsr cbm.CHRIN
cmp #$0d ; return (ascii 13) pressed?
beq + ; yes, end.
sta (P8ZP_SCRATCH_W1),y ; else store char in buffer
iny
bne -
+ lda #0
sta (P8ZP_SCRATCH_W1),y ; finish string with 0 byte
rts
}}
}
asmsub petscii2scr(ubyte petscii_char @A) -> ubyte @A {
; -- convert petscii character to screencode
%asm {{
sta P8ZP_SCRATCH_REG
lsr a
lsr a
lsr a
lsr a
lsr a
tax
lda _offsets,x
eor P8ZP_SCRATCH_REG
rts
_offsets .byte 128, 0, 64, 32, 64, 192, 128, 128
}}
}
asmsub petscii2scr_str(str petscii_string @AY) {
; -- convert petscii string to screencodes string
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
jsr petscii2scr
sta (P8ZP_SCRATCH_W1),y
iny
bne -
+ rts
}}
}
sub print_bool(bool value) {
if value
txt.print("true")
else
txt.print("false")
}
asmsub print_ub0 (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
jsr conv.internal_ubyte2decimal
pha
tya
jsr cbm.CHROUT
txa
jsr cbm.CHROUT
pla
jmp cbm.CHROUT
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,X,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
jsr conv.internal_ubyte2decimal
_print_byte_digits
pha
cpy #'0'
beq +
tya
jsr cbm.CHROUT
txa
jsr cbm.CHROUT
jmp _ones
+ cpx #'0'
beq _ones
txa
jsr cbm.CHROUT
_ones pla
jmp cbm.CHROUT
}}
}
asmsub print_b (byte value @ A) clobbers(A,X,Y) {
; ---- print the byte in A in decimal form, without left padding 0s
%asm {{
pha
cmp #0
bpl +
lda #'-'
jsr cbm.CHROUT
+ pla
jsr conv.internal_byte2decimal
jmp print_ub._print_byte_digits
}}
}
}

View File

@ -2,6 +2,11 @@ floats {
; the floating point functions shared across compiler targets
%option merge, no_symbol_prefixing, ignore_unused
const float π = 3.141592653589793
const float PI = π
const float TWOPI = 2*π
asmsub print(float value @FAC1) clobbers(A,X,Y) {
; ---- prints the floating point value (without a newline). No leading space (unlike BASIC)!
%asm {{
@ -88,6 +93,29 @@ sub atan(float value) -> float {
}}
}
; two-argument arctangent that returns an angle in the correct quadrant
; for the signs of x and y, normalized to the range [0, 2π]
sub atan2(float y, float x) -> float {
float atn
if x == 0 {
atn = π/2
if y == 0 return 0
if y < 0 {
atn += π
}
} else {
atn = atan(y / x)
}
if x < 0 atn += π
if atn < 0 atn += 2*π
return atn
}
; reciprocal functions
sub secant(float value) -> float { return 1.0 / cos(value) }
sub csc(float value) -> float { return 1.0 / sin(value) }
sub cot(float value) -> float { return 1.0 / tan(value) }
sub ln(float value) -> float {
%asm {{
lda #<value

View File

@ -1,46 +0,0 @@
txt {
; the textio functions shared across compiler targets
%option merge, no_symbol_prefixing, ignore_unused
asmsub petscii2scr(ubyte petscii_char @A) -> ubyte @A {
; -- convert petscii character to screencode
%asm {{
sta P8ZP_SCRATCH_REG
lsr a
lsr a
lsr a
lsr a
lsr a
tax
lda _offsets,x
eor P8ZP_SCRATCH_REG
rts
_offsets .byte 128, 0, 64, 32, 64, 192, 128, 128
}}
}
asmsub petscii2scr_str(str petscii_string @AY) {
; -- convert petscii string to screencodes string
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
jsr petscii2scr
sta (P8ZP_SCRATCH_W1),y
iny
bne -
+ rts
}}
}
sub print_bool(bool value) {
if value
txt.print("true")
else
txt.print("false")
}
}

View File

@ -145,6 +145,12 @@ diskio {
return "unknown"
}
sub status_code() -> ubyte {
; -- return status code instead of whole CBM-DOS status string. (in this case always 255, which means 'unable to return sensible value')
return 255
}
sub save(uword filenameptr, uword start_address, uword savesize) -> bool {
%ir {{
load.b r65532,0

View File

@ -1,5 +1,5 @@
; Emulator debug interface. (reflecting the Commander X16 emudbg library module)
; Docs: https://github.com/X16Community/x16-emulator#emulator-io-registers
; Docs: https://github.com/X16Community/x16-emulator/tree/d52f118ce893fa24c4ca021a0b8de46cb80e5ccf#emulator-io-registers
%import textio

View File

@ -80,6 +80,20 @@ sub atan(float value) -> float {
}}
}
; two-argument arctangent that returns an angle in the correct quadrant
; for the signs of x and y, normalized to the range [0, 2π]
sub atan2(float y, float x) -> float {
float atn = atan(y / x)
if x < 0 atn += π
if atn < 0 atn += 2*π
return atn
}
; reciprocal functions
sub secant(float value) -> float { return 1.0 / cos(value) }
sub csc(float value) -> float { return 1.0 / sin(value) }
sub cot(float value) -> float { return 1.0 / tan(value) }
sub ln(float value) -> float {
%ir {{
loadm.f fr0,floats.ln.value

View File

@ -56,6 +56,7 @@ string {
sub find(str st, ubyte character) -> ubyte {
; Locates the first position of the given character in the string,
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
; NOTE: because this isn't an asmsub, there's only a SINGLE return value here. On the c64/cx16 targets etc there are 2 return values.
ubyte ix
for ix in 0 to length(st)-1 {
if st[ix]==character {

View File

@ -28,6 +28,10 @@ sub clear_screen() {
}}
}
sub cls() {
clear_screen()
}
sub nl() {
chrout('\n')
}

View File

@ -43,6 +43,7 @@ private fun compileMain(args: Array<String>): Boolean {
val startEmulator2 by cli.option(ArgType.Boolean, fullName = "emu2", description = "auto-start alternative emulator after successful compilation")
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
val dumpVariables by cli.option(ArgType.Boolean, fullName = "dumpvars", description = "print a dump of the variables in the program")
val dumpSymbols by cli.option(ArgType.Boolean, fullName = "dumpsymbols", description = "print a dump of the variable declarations and subroutine signatures")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val noStrictBool by cli.option(ArgType.Boolean, fullName = "nostrictbool", description = "allow implicit conversions between bool and bytes")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform code optimizations")
@ -55,7 +56,9 @@ private fun compileMain(args: Array<String>): Boolean {
val printAst1 by cli.option(ArgType.Boolean, fullName = "printast1", description = "print out the compiler AST")
val printAst2 by cli.option(ArgType.Boolean, fullName = "printast2", description = "print out the intermediate AST that is used for code generation")
val breakpointCpuInstruction by cli.option(ArgType.Choice(listOf("brk", "stp"), { it }), fullName = "breakinstr", description = "the CPU instruction to use as well for %breakpoint")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${PETTarget.NAME}', '${VMTarget.NAME}') (required)")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of ${CompilationTargets.joinToString(",")}) (required)")
val bytes2float by cli.option(ArgType.String, fullName = "bytes2float", description = "convert a comma separated list of bytes from the target system to a float value. NOTE: you need to supply a target option too, and also still have to supply a dummy module file name as well!")
val float2bytes by cli.option(ArgType.String, fullName = "float2bytes", description = "convert floating point number to a list of bytes for the target system. NOTE: you need to supply a target option too, and also still have to supply a dummy module file name as well!")
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
val varsGolden by cli.option(ArgType.Boolean, fullName = "varsgolden", description = "put uninitialized variables in 'golden ram' memory area instead of at the end of the program. On the cx16 target this is $0400-07ff. This is unavailable on other systems.")
@ -93,12 +96,21 @@ private fun compileMain(args: Array<String>): Boolean {
return false
}
if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME, AtariTarget.NAME, PETTarget.NAME, VMTarget.NAME)) {
if (compilationTarget !in CompilationTargets) {
System.err.println("Invalid compilation target: $compilationTarget")
return false
}
}
if(bytes2float!=null) {
convertBytesToFloat(bytes2float!!, compilationTarget!!)
return true
}
if(float2bytes!=null) {
convertFloatToBytes(float2bytes!!, compilationTarget!!)
return true
}
if(varsHighBank==0 && compilationTarget==Cx16Target.NAME) {
System.err.println("On the Commander X16, HiRAM bank 0 is used by the kernal and can't be used.")
return false
@ -131,7 +143,8 @@ private fun compileMain(args: Array<String>): Boolean {
}
if(startVm==true) {
return runVm(moduleFiles.first())
runVm(moduleFiles.first())
return true
}
val processedSymbols = processSymbolDefs(symbolDefs) ?: return false
@ -155,6 +168,7 @@ private fun compileMain(args: Array<String>): Boolean {
includeSourcelines == true,
experimentalCodegen == true,
dumpVariables == true,
dumpSymbols == true,
varsHighBank,
varsGolden == true,
slabsHighBank,
@ -235,6 +249,7 @@ private fun compileMain(args: Array<String>): Boolean {
includeSourcelines == true,
experimentalCodegen == true,
dumpVariables == true,
dumpSymbols==true,
varsHighBank,
varsGolden == true,
slabsHighBank,
@ -287,6 +302,21 @@ private fun compileMain(args: Array<String>): Boolean {
return true
}
fun convertFloatToBytes(number: String, target: String) {
val tgt = getCompilationTargetByName(target)
val dbl = number.toDouble()
val bytes = tgt.machine.convertFloatToBytes(dbl)
print("$dbl in bytes on '$target': ")
println(bytes.joinToString(","))
}
fun convertBytesToFloat(bytelist: String, target: String) {
val tgt = getCompilationTargetByName(target)
val bytes = bytelist.split(',').map { it.trim().toUByte() }
val number = tgt.machine.convertBytesToFloat(bytes)
println("floating point value on '$target': $number")
}
private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
val result = mutableMapOf<String, String>()
val defPattern = """(.+)\s*=\s*(.+)""".toRegex()
@ -302,9 +332,8 @@ private fun processSymbolDefs(symbolDefs: List<String>): Map<String, String>? {
return result
}
fun runVm(irFilename: String): Boolean {
fun runVm(irFilename: String) {
val irFile = Path(irFilename)
val vmdef = VirtualMachineDefinition()
vmdef.launchEmulator(0, irFile)
return true
}

View File

@ -7,13 +7,17 @@ import prog8.ast.base.AstException
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteral
import prog8.ast.printProgram
import prog8.ast.printSymbols
import prog8.ast.statements.Directive
import prog8.code.SymbolTableMaker
import prog8.code.ast.PtProgram
import prog8.code.ast.printAst
import prog8.code.core.*
import prog8.code.optimize.optimizeIntermediateAst
import prog8.code.target.*
import prog8.code.target.AtariTarget
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8.code.target.getCompilationTargetByName
import prog8.codegen.vm.VmCodeGen
import prog8.compiler.astprocessing.*
import prog8.optimizer.*
@ -22,6 +26,7 @@ import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.nameWithoutExtension
import kotlin.math.round
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
@ -39,6 +44,7 @@ class CompilerArguments(val filepath: Path,
val includeSourcelines: Boolean,
val experimentalCodegen: Boolean,
val dumpVariables: Boolean,
val dumpSymbols: Boolean,
val varsHighBank: Int?,
val varsGolden: Boolean,
val slabsHighBank: Int?,
@ -57,21 +63,11 @@ class CompilerArguments(val filepath: Path,
fun compileProgram(args: CompilerArguments): CompilationResult? {
val compTarget =
when(args.compilationTarget) {
C64Target.NAME -> C64Target()
C128Target.NAME -> C128Target()
Cx16Target.NAME -> Cx16Target()
PETTarget.NAME -> PETTarget()
AtariTarget.NAME -> AtariTarget()
VMTarget.NAME -> VMTarget()
else -> throw IllegalArgumentException("invalid compilation target")
}
val compTarget = getCompilationTargetByName(args.compilationTarget)
var compilationOptions: CompilationOptions
var ast: PtProgram? = null
var resultingProgram: Program? = null
var importedFiles: List<Path> = emptyList()
var importedFiles: List<Path>
try {
val totalTime = measureTimeMillis {
@ -86,6 +82,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
includeSourcelines = args.includeSourcelines
experimentalCodegen = args.experimentalCodegen
dumpVariables = args.dumpVariables
dumpSymbols = args.dumpSymbols
breakpointCpuInstruction = args.breakpointCpuInstruction
varsHighBank = args.varsHighBank
varsGolden = args.varsGolden
@ -134,8 +131,12 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
}
val intermediateAst = IntermediateAstMaker(program, args.errors).transform()
optimizeIntermediateAst(intermediateAst, compilationOptions, args.errors)
args.errors.report()
val stMaker = SymbolTableMaker(intermediateAst, compilationOptions)
val symbolTable = stMaker.make()
if(compilationOptions.optimize) {
optimizeIntermediateAst(intermediateAst, compilationOptions, symbolTable, args.errors)
args.errors.report()
}
if(args.printAst2) {
println("\n*********** INTERMEDIATE AST *************")
@ -398,6 +399,12 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
program.preprocessAst(errors, compilerOptions)
if(compilerOptions.dumpSymbols) {
printSymbols(program)
exitProcess(0)
}
program.checkIdentifiers(errors, compilerOptions)
errors.report()
program.charLiteralsToUByteLiterals(compilerOptions.compTarget, errors)
@ -444,8 +451,13 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
break
}
removeUnusedCode(program, errors, compilerOptions)
if(errors.noErrors())
if(errors.noErrors()) {
// last round of optimizations because constFold may have enabled more...
program.simplifyExpressions(errors, compilerOptions)
program.optimizeStatements(errors, functions, compilerOptions)
program.constantFold(errors, compilerOptions) // because simplified statements and expressions can result in more constants that can be folded away
}
errors.report()
}
@ -477,8 +489,10 @@ private fun createAssemblyAndAssemble(program: PtProgram,
else
throw NotImplementedError("no code generator for cpu ${compilerOptions.compTarget.machine.cpu}")
// need to make a new symboltable here to capture possible changes made by optimization steps performed earlier!
val stMaker = SymbolTableMaker(program, compilerOptions)
val symbolTable = stMaker.make()
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
errors.report()

View File

@ -52,6 +52,8 @@ internal class AstChecker(private val program: Program,
override fun visit(module: Module) {
super.visit(module)
if(module.name.startsWith('_'))
errors.err("identifiers cannot start with an underscore", module.position)
val directives = module.statements.filterIsInstance<Directive>().groupBy { it.directive }
directives.filter { it.value.size > 1 }.forEach{ entry ->
when(entry.key) {
@ -62,6 +64,18 @@ internal class AstChecker(private val program: Program,
}
override fun visit(identifier: IdentifierReference) {
if(identifier.nameInSource.any { it.startsWith('_') }) {
errors.err("identifiers cannot start with an underscore", identifier.position)
}
if(identifier.nameInSource.any { it=="void" }) {
// 'void' as "identifier" is only allowed as part of a multi-assignment expression
if (!(identifier.nameInSource == listOf("void") && (identifier.parent as? AssignTarget)?.multi?.isNotEmpty() == true
|| identifier.parent is AssignTarget && (identifier.parent.parent as? AssignTarget)?.multi?.isNotEmpty() == true)
) {
errors.err("identifiers cannot contain the 'void' keyword", identifier.position)
}
}
checkLongType(identifier)
val stmt = identifier.targetStatement(program)
if(stmt==null)
@ -116,9 +130,7 @@ internal class AstChecker(private val program: Program,
}
if(expectedReturnValues.size==1 && returnStmt.value!=null) {
val valueDt = returnStmt.value!!.inferType(program)
if(!valueDt.isKnown) {
errors.err("return value type mismatch or unknown symbol", returnStmt.value!!.position)
} else {
if(valueDt.isKnown) {
if (expectedReturnValues[0] != valueDt.getOr(DataType.UNDEFINED)) {
if(valueDt istype DataType.BOOL && expectedReturnValues[0] == DataType.UBYTE) {
// if the return value is a bool and the return type is ubyte, allow this. But give a warning.
@ -266,6 +278,9 @@ internal class AstChecker(private val program: Program,
}
override fun visit(block: Block) {
if(block.name.startsWith('_'))
errors.err("identifiers cannot start with an underscore", block.position)
val addr = block.address
if(addr!=null && addr>65535u) {
errors.err("block memory address must be valid integer 0..\$ffff", block.position)
@ -295,6 +310,9 @@ internal class AstChecker(private val program: Program,
}
override fun visit(label: Label) {
if(label.name.startsWith('_'))
errors.err("identifiers cannot start with an underscore", label.position)
// scope check
if(label.parent !is Block && label.parent !is Subroutine && label.parent !is AnonymousScope) {
errors.err("Labels can only be defined in the scope of a block, a loop body, or within another subroutine", label.position)
@ -336,6 +354,9 @@ internal class AstChecker(private val program: Program,
override fun visit(subroutine: Subroutine) {
fun err(msg: String) = errors.err(msg, subroutine.position)
if(subroutine.name.startsWith('_'))
errors.err("identifiers cannot start with an underscore", subroutine.position)
if(subroutine.name in BuiltinFunctions)
err("cannot redefine a built-in function")
@ -474,6 +495,9 @@ internal class AstChecker(private val program: Program,
// Non-string and non-ubytearray Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD).
for(p in subroutine.parameters) {
if(p.name.startsWith('_'))
errors.err("identifiers cannot start with an underscore", p.position)
if(p.type in PassByReferenceDatatypes && p.type !in listOf(DataType.STR, DataType.ARRAY_UB)) {
errors.err("this pass-by-reference type can't be used as a parameter type. Instead, use an uword to receive the address, or access the variable from the outer scope directly.", p.position)
}
@ -517,32 +541,78 @@ internal class AstChecker(private val program: Program,
}
override fun visit(assignment: Assignment) {
val targetDt = assignment.target.inferType(program)
val valueDt = assignment.value.inferType(program)
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
if(targetDt.isIterable)
errors.err("cannot assign value to string or array", assignment.value.position)
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) {
if(targetDt.isUnknown) {
if(assignment.target.identifier?.targetStatement(program)!=null)
errors.err("target datatype is unknown", assignment.target.position)
// otherwise, another error about missing symbol is already reported.
fun checkType(target: AssignTarget, value: Expression, augmentable: Boolean) {
val targetDt = target.inferType(program)
val valueDt = value.inferType(program)
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) {
if(targetDt.isIterable)
errors.err("cannot assign value to string or array", value.position)
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) {
if(targetDt.isUnknown) {
if(target.identifier?.targetStatement(program)!=null)
errors.err("target datatype is unknown", target.position)
// otherwise, another error about missing symbol is already reported.
}
}
}
if(value is TypecastExpression) {
if(augmentable && targetDt istype DataType.FLOAT)
errors.err("typecasting a float value in-place makes no sense", value.position)
}
val numvalue = value.constValue(program)
if(numvalue!=null && targetDt.isKnown)
checkValueTypeAndRange(targetDt.getOr(DataType.UNDEFINED), numvalue)
}
if(assignment.target.multi==null) {
checkType(assignment.target, assignment.value, assignment.isAugmentable)
}
if(assignment.target.void && assignment.target.multi?.isNotEmpty()!=true) {
if(assignment.value is IFunctionCall)
errors.err("cannot assign to 'void', perhaps a void function call was intended", assignment.position)
else
errors.err("cannot assign to 'void'", assignment.position)
return
}
val fcall = assignment.value as? IFunctionCall
val fcallTarget = fcall?.target?.targetSubroutine(program)
if(assignment.target.multi!=null) {
checkMultiAssignment(assignment, fcall, fcallTarget)
} else if(fcallTarget!=null) {
if(fcallTarget.returntypes.size!=1) {
errors.err("number of assignment targets doesn't match number of return values from the subroutine", fcall.position)
return
}
}
if(assignment.value is TypecastExpression) {
if(assignment.isAugmentable && targetDt istype DataType.FLOAT)
errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
}
val numvalue = assignment.value.constValue(program)
if(numvalue!=null && targetDt.isKnown)
checkValueTypeAndRange(targetDt.getOr(DataType.UNDEFINED), numvalue)
super.visit(assignment)
}
private fun checkMultiAssignment(assignment: Assignment, fcall: IFunctionCall?, fcallTarget: Subroutine?) {
// multi-assign: check the number of assign targets vs. the number of return values of the subroutine
// also check the types of the variables vs the types of each return value
if(fcall==null || fcallTarget==null) {
errors.err("expected a function call with multiple return values", assignment.value.position)
return
}
val targets = assignment.target.multi!!
if(fcallTarget.returntypes.size!=targets.size) {
errors.err("number of assignment targets doesn't match number of return values from the subroutine", fcall.position)
return
}
fcallTarget.returntypes.zip(targets).withIndex().forEach { (index, p) ->
val (returnType, target) = p
val targetDt = target.inferType(program).getOr(DataType.UNDEFINED)
if (!target.void && !(returnType isAssignableTo targetDt))
errors.err("can't assign returnvalue #${index + 1} to corresponding target; $returnType vs $targetDt", target.position)
}
}
override fun visit(assignTarget: AssignTarget) {
super.visit(assignTarget)
@ -552,6 +622,9 @@ internal class AstChecker(private val program: Program,
errors.err("address out of range", assignTarget.position)
}
if(assignTarget.parent is AssignTarget)
return // sub-target of a multi-assign is tested elsewhere
val assignment = assignTarget.parent as Statement
val targetIdentifier = assignTarget.identifier
if (targetIdentifier != null) {
@ -771,6 +844,11 @@ internal class AstChecker(private val program: Program,
if(compilerOptions.zeropage==ZeropageType.DONTUSE && decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE)
err("zeropage usage has been disabled by options")
if(decl.splitArray) {
if (decl.datatype !in arrayOf(DataType.ARRAY_W, DataType.ARRAY_UW, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT)) {
errors.err("split can only be used on word arrays", decl.position)
}
}
super.visit(decl)
}
@ -873,7 +951,7 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur at module level")
val allowedEncodings = Encoding.entries.map {it.prefix}
if(directive.args.size!=1 || directive.args[0].name !in allowedEncodings)
err("invalid encoding directive, expected one of ${allowedEncodings}")
err("invalid encoding directive, expected one of $allowedEncodings")
}
else -> throw SyntaxError("invalid directive ${directive.directive}", directive.position)
}
@ -1174,25 +1252,6 @@ internal class AstChecker(private val program: Program,
if(error!=null)
errors.err(error.first, error.second)
// check the functions that return multiple returnvalues.
val stmt = functionCallExpr.target.targetStatement(program)
if (stmt is Subroutine) {
if (stmt.returntypes.size > 1) {
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
// asmsub routines can have multiple return values, for instance in 2 different registers.
// It's not (yet) possible to handle these multiple return values because assignments
// are only to a single unique target at the same time.
// EXCEPTION:
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
// it *is* possible to handle them by just actually assigning the register value and
// dealing with the status bit as just being that, the status bit after the call.
val (returnRegisters, _) = stmt.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
if (returnRegisters.size>1) {
errors.err("It's not possible to store the multiple result values of this asmsub call; you should use a small block of custom inline assembly for this.", functionCallExpr.position)
}
}
}
// functions that don't return a value, can't be used in an expression or assignment
if(targetStatement is Subroutine) {
if(targetStatement.returntypes.isEmpty()) {
@ -1264,7 +1323,14 @@ internal class AstChecker(private val program: Program,
if(funcName[0]=="setlsb" || funcName[0]=="setmsb") {
val firstArg = functionCallStatement.args[0]
if(firstArg !is IdentifierReference && firstArg !is ArrayIndexedExpression)
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)
errors.err("invalid argument to a in-place modifying function", firstArg.position)
} else if(funcName[0]=="divmod" || funcName[0].startsWith("divmod__")) {
val thirdArg = functionCallStatement.args[2]
val fourthArg = functionCallStatement.args[3]
if(thirdArg !is IdentifierReference && thirdArg !is ArrayIndexedExpression)
errors.err("invalid argument to a in-place modifying function", thirdArg.position)
if(fourthArg !is IdentifierReference && fourthArg !is ArrayIndexedExpression)
errors.err("invalid argument to a in-place modifying function", fourthArg.position)
} else {
if(functionCallStatement.args.any { it !is IdentifierReference && it !is ArrayIndexedExpression && it !is DirectMemoryRead })
errors.err("invalid argument to a in-place modifying function", functionCallStatement.args.first().position)

View File

@ -121,7 +121,7 @@ class AstPreprocessor(val program: Program,
// we need to handle multi-decl here too, the desugarer maybe has not processed it here yet...
if(decl.value!=null) {
decl.names.forEach { name ->
val target = AssignTarget(IdentifierReference(listOf(name), decl.position), null, null, decl.position)
val target = AssignTarget(IdentifierReference(listOf(name), decl.position), null, null, null, false, decl.position)
val assign = Assignment(target.copy(), decl.value!!.copy(), AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.InsertAfter(decl, assign, scope))
}
@ -137,7 +137,7 @@ class AstPreprocessor(val program: Program,
} else {
// handle declaration of a single variable
if(decl.value!=null && (decl.datatype in NumericDatatypes || decl.datatype==DataType.BOOL)) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, null, false, decl.position)
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null

View File

@ -183,7 +183,7 @@ _after:
}
if(functionCall.target.nameInSource==listOf("poke")) {
// poke(a, v) is synonymous with @(a) = v
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), null, false, position)
val assign = Assignment(tgt, functionCall.args[1], AssignmentOrigin.OPTIMIZER, position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
}
@ -222,7 +222,7 @@ _after:
return if(parent is AssignTarget) {
// assignment to array
val memwrite = DirectMemoryWrite(address, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, arrayIndexedExpression.position)
val newtarget = AssignTarget(null, null, memwrite, null, false, arrayIndexedExpression.position)
listOf(IAstModification.ReplaceNode(parent, newtarget, parent.parent))
} else {
// read from array

View File

@ -90,6 +90,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
private fun transform(srcAssign: Assignment): PtNode {
if(srcAssign.isAugmentable) {
require(srcAssign.target.multi==null)
val srcExpr = srcAssign.value
val (operator: String, augmentedValue: Expression?) = when(srcExpr) {
is BinaryExpression -> {
@ -136,20 +137,25 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
}
val assign = PtAssignment(srcAssign.position)
assign.add(transform(srcAssign.target))
val multi = srcAssign.target.multi
if(multi==null) {
assign.add(transform(srcAssign.target))
} else {
multi.forEach { target -> assign.add(transform(target)) }
}
assign.add(transformExpression(srcAssign.value))
return assign
}
private fun transform(srcTarget: AssignTarget): PtAssignTarget {
val target = PtAssignTarget(srcTarget.position)
val target = PtAssignTarget(srcTarget.void, srcTarget.position)
if(srcTarget.identifier!=null)
target.add(transform(srcTarget.identifier!!))
else if(srcTarget.arrayindexed!=null)
target.add(transform(srcTarget.arrayindexed!!))
else if(srcTarget.memoryAddress!=null)
target.add(transform(srcTarget.memoryAddress!!))
else
else if(!srcTarget.void)
throw FatalAstException("invalid AssignTarget")
return target
}
@ -264,7 +270,7 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
private fun transform(srcCall: FunctionCallStatement): PtFunctionCall {
val (target, type) = srcCall.target.targetNameAndType(program)
val call = PtFunctionCall(target,true, type, srcCall.position)
val call = PtFunctionCall(target, true, type, srcCall.position)
for (arg in srcCall.args)
call.add(transformExpression(arg))
return call
@ -272,11 +278,8 @@ class IntermediateAstMaker(private val program: Program, private val errors: IEr
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
val (target, _) = srcCall.target.targetNameAndType(program)
val type = srcCall.inferType(program).getOrElse {
throw FatalAstException("unknown dt $srcCall")
}
val isVoid = type==DataType.UNDEFINED
val call = PtFunctionCall(target, isVoid, type, srcCall.position)
val iType = srcCall.inferType(program)
val call = PtFunctionCall(target, iType.isUnknown && srcCall.parent !is Assignment, iType.getOrElse { DataType.UNDEFINED }, srcCall.position)
for (arg in srcCall.args)
call.add(transformExpression(arg))
return call

View File

@ -74,6 +74,13 @@ internal class LiteralsToAutoVars(private val program: Program, private val erro
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.names.size>1) {
val fcallTarget = (decl.value as? IFunctionCall)?.target?.targetSubroutine(program)
if(fcallTarget!=null && fcallTarget.returntypes.size>1) {
errors.err("ambiguous multi-variable initialization. Use separate variable declaration and assignment(s) instead.", decl.value!!.position)
return noModifications
}
// note: the desugaring of a multi-variable vardecl has to be done here
// and not in CodeDesugarer, that one is too late (identifiers can't be found otherwise)
if(decl.datatype !in NumericDatatypesWithBoolean)

View File

@ -60,7 +60,8 @@ internal class StatementReorderer(
// Add assignment to initialize with zero
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
val identifier = IdentifierReference(listOf(decl.name), decl.position)
val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position)
val assignzero = Assignment(AssignTarget(identifier, null, null, null, false, decl.position),
decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position)
return listOf(IAstModification.InsertAfter(
decl, assignzero, parent as IStatementContainer
))
@ -72,7 +73,8 @@ internal class StatementReorderer(
// So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99'
val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos)
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, AssignmentOrigin.VARINIT, pos)
val assign = Assignment(AssignTarget(identifier, null, null, null, false, pos),
decl.value!!, AssignmentOrigin.VARINIT, pos)
decl.value = null
return listOf(IAstModification.InsertAfter(
decl, assign, parent as IStatementContainer
@ -90,7 +92,8 @@ internal class StatementReorderer(
if(target!=null && target.isArray) {
val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos)
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, AssignmentOrigin.VARINIT, pos)
val assign = Assignment(AssignTarget(identifier, null, null, null, false, pos),
decl.value!!, AssignmentOrigin.VARINIT, pos)
decl.value = null
return listOf(IAstModification.InsertAfter(
decl, assign, parent as IStatementContainer

View File

@ -332,8 +332,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter type
val modifications = mutableListOf<IAstModification>()
val sub = call.target.targetStatement(program)
val params = when(sub) {
val params = when(val sub = call.target.targetStatement(program)) {
is BuiltinFunctionPlaceholder -> BuiltinFunctions.getValue(sub.name).parameters
is Subroutine -> sub.parameters.map { FParam(it.name, listOf(it.type).toTypedArray()) }
else -> emptyList()
@ -463,14 +462,16 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
override fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> {
val conditionDt = (whenChoice.parent as When).condition.inferType(program)
val values = whenChoice.values
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
val valueDt = value.inferType(program)
if(valueDt!=conditionDt) {
val castedValue = value.typecastTo(conditionDt.getOr(DataType.UNDEFINED), valueDt.getOr(DataType.UNDEFINED), true)
if(castedValue.first) {
castedValue.second.linkParents(whenChoice)
values[index] = castedValue.second
if(conditionDt.isKnown) {
val values = whenChoice.values
values?.toTypedArray()?.withIndex()?.forEach { (index, value) ->
val valueDt = value.inferType(program)
if(valueDt!=conditionDt) {
val castedValue = value.typecastTo(conditionDt.getOr(DataType.UNDEFINED), valueDt.getOr(DataType.UNDEFINED), true)
if(castedValue.first) {
castedValue.second.linkParents(whenChoice)
values[index] = castedValue.second
}
}
}
}

View File

@ -156,28 +156,6 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
}
/* TODO: is this really no longer needed in boolean branch?
if(expr.operator in LogicalOperators) {
// remove redundant !=0 comparisons from logical expressions such as: a!=0 xor b --> a xor b (only for byte operands!)
val leftExpr = expr.left as? BinaryExpression
if(leftExpr != null &&
leftExpr.operator == "!=" &&
!leftExpr.left.isSimple &&
leftExpr.left.inferType(program).isBytes &&
leftExpr.right.constValue(program)?.number == 0.0) {
return listOf(IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr))
}
val rightExpr = expr.right as? BinaryExpression
if(rightExpr != null &&
rightExpr.operator == "!=" &&
!rightExpr.left.isSimple &&
rightExpr.left.inferType(program).isBytes &&
rightExpr.right.constValue(program)?.number == 0.0) {
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
}
}
*/
return noModifications
}
@ -227,18 +205,20 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
fun checkArray(variable: VarDecl): Iterable<IAstModification> {
return if(variable.value==null) {
val arraySpec = variable.arraysize!!
val size = arraySpec.indexExpr.constValue(program)?.number?.toInt() ?: throw FatalAstException("no array size")
return if(size==0)
replaceWithFalse()
else
noModifications
return when (variable.value) {
null -> {
val arraySpec = variable.arraysize!!
val size = arraySpec.indexExpr.constValue(program)?.number?.toInt() ?: throw FatalAstException("no array size")
return if(size==0)
replaceWithFalse()
else
noModifications
}
is ArrayLiteral -> {
checkArray((variable.value as ArrayLiteral).value)
}
else -> noModifications
}
else if(variable.value is ArrayLiteral) {
checkArray((variable.value as ArrayLiteral).value)
}
else noModifications
}
fun checkString(stringVal: StringLiteral): Iterable<IAstModification> {

View File

@ -114,12 +114,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
}
if(target.isAsmSubroutine) {
if(target.asmReturnvaluesRegisters.size>1) {
// multiple return values will NOT work inside an expression.
// they MIGHT work in a regular assignment or just a function call statement.
// EXCEPTION:
// if the asmsub returns multiple values and one of them is via a status register bit (such as carry),
// it *is* possible to handle them by just actually assigning the register value and
// dealing with the status bit as just being that, the status bit after the call.
// multiple return values will NOT work inside an expression. Use an assignment first.
val parent = if(call is Statement) call.parent else if(call is Expression) call.parent else null
if (call !is FunctionCallStatement) {
val checkParent =
@ -128,8 +123,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
else
parent
if (checkParent !is Assignment && checkParent !is VarDecl) {
val (returnRegisters, _) = target.asmReturnvaluesRegisters.partition { rr -> rr.registerOrPair != null }
if (returnRegisters.size>1) {
if (target.asmReturnvaluesRegisters.size>1) {
return Pair("can't use subroutine call that returns multiple return values here", call.position)
}
}

View File

@ -1,9 +1,13 @@
package prog8tests
import io.kotest.core.config.AbstractProjectConfig
import io.kotest.core.spec.SpecExecutionOrder
import io.kotest.core.test.TestCaseOrder
object ProjectConfig : AbstractProjectConfig() {
override val parallelism = kotlin.math.max(1, Runtime.getRuntime().availableProcessors() / 2)
override val testCaseOrder = TestCaseOrder.Lexicographic
override val specExecutionOrder = SpecExecutionOrder.Lexicographic
override val parallelism = kotlin.math.max(1, Runtime.getRuntime().availableProcessors()-1)
// override fun listeners() = listOf(SystemOutToNullListener)
}

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